在我的基于文档的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;
}
在内容已加载中,进度窗口关闭。
- (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(可以是数百 MB!
首先,您是否研究过NSURLSessionTask的进度报告API?有很多工具用于测量和在数据加载时回调。您可能可以避免进行轮询,而只是在回调这些 UI 时更新您的 UI - 所有这些都在主线程上。
事实上,你甚至可能根本不需要一个。我记得,无论如何,NSURLSession执行的网络操作都是在后台完成的。我敢打赌,你可以删除所有这些,只需使用来自NSURLSession API的一些额外的委托回调来完成这项工作。也更简单:)
祝你好运!