在异步加载文件的同时更新NSWindow内容

时间:2018-06-29 12:03:54

标签: macos user-interface grand-central-dispatch appkit nsprogressindicator

在基于文档的MacOS应用程序中,我已经加载了一些大文件(尤其是在应用程序启动时打开的最近文件)。我创建了ProgressController(一个NSWindowController子类)以通知用户窗口正在加载文件。它由我用来管理文档的NSDocument子类的makeWindowControllers方法分配。它们是在用户打开文件时创建的(特别是在启动时,当用户退出应用程序时显示文档),并且它们在后台队列中异步加载其内容,这在原则上不会影响主线程的性能:

-(instancetype) initForURL:(NSURL *)urlOrNil withContentsOfURL:(NSURL *)contentsURL ofType:(NSString *)typeName error:(NSError *
{
  if (self = [super init]){
    ... assign some variables before loading content...
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0), ^{

            [[[NSURLSession sharedSession] dataTaskWithURL:urlOrNil completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                self.content =  [[NSMutableString alloc] initWithData:data encoding:encoder];
                dispatch_async(dispatch_get_main_queue(), ^(){
                    [self contentIsLoaded];
                });
            }] resume];
        });
  }
  return self;
}

在contentIsLoaded中,进度窗口关闭。

- (void) contentIsLoaded
{
    ... do something ...
    [self.progressController close];
}

此行为正常,显示窗口,并在必要时关闭。 当我想更新主队列上此窗口的内容时,会发生问题。我尝试设置NSTimer,但是即使它在主队列中创建,也不会被触发。因此,在MyApplicationDelegate中,我创建了一个GCD计时器,如下所示:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
             if (self->timer !=nil) dispatch_source_cancel(timer);
             self.queue =  dispatch_queue_create( "my session queue", DISPATCH_QUEUE_CONCURRENT);
             timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue);
             dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0);
             dispatch_source_set_event_handler(timer, ^{
                [self updateProgress];
             });
             dispatch_resume(timer);
            …
    }

在MyApplicationDelegate中,updateProgress方法定义为:

- (void) updateProgress
{
    dispatch_async(self.queue, ^{
        NSLog(@"timer method fired");
        dispatch_async(dispatch_get_main_queue(), ^(){
            NSLog(@"access to UI fired");
            ... update window (e.g. NSProgressIndicator)
            }
        });
        if (self.shouldCancelTimer) dispatch_source_cancel(self->timer);
    });
}

运行应用程序时,每秒记录一次“ Timer method fired”。 "timer method fired"消息(在主队列中)仅记录一次或两次,然后该记录似乎被挂起,直到文件已加载。然后,由于之前已挂起,此丢失的消息连续出现几次。

我错了什么?我认为用于文件加载的后台队列不应影响主队列和UI更新。许多应用程序的行为都是这样,我需要这种行为,因为我的应用程序中的文件(字符串,csv,json)可能是数百兆字节!

1 个答案:

答案 0 :(得分:0)

对于初学者来说,您是否研究过NSURLSessionTask的进度报告API?有很多工具可以测量和调用数据负载。您有可能避免进行轮询,而只需在回调这些主界面时更新您的用户界面-全部在主线程上。

实际上,您甚至根本不需要一个。我记得,无论如何,NSURLSession进行的联网操作都是在后台完成的。我敢打赌,您可以删除所有这些内容,而只需使用NSURLSession API中的一些额外的委托回调即可完成此操作。也更简单:)

祝你好运!