所以我正在主线程上创建我的下载
NSURLRequest *request = [NSURLRequest requestWithURL:download.URL]; NSURLSessionDownloadTask *downloadTask = [self.downloadSession downloadTaskWithRequest:request]; [downloadTask resume];
并将与下载相关联的NSManagedContextID添加到NSMutableDictionary,
所以我可以稍后在委托回调中检索它
[self.downloads setObject:[download objectID] forKey:[NSNumber numberWithInteger:downloadTask.taskIdentifier]];
我上面的self.downloadSession是这样配置的
- (NSURLSession *)backgroundSession { static NSURLSession *session = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken,^{ NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.test.backgroundSession"]; configuration.discretionary = YES; session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; }); return session; }
我的问题是代理回调似乎在不同的线程上被调用
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { NSManagedObjectID *downloadID = [self.downloads objectForKey:[NSNumber numberWithInteger:downloadTask.taskIdentifier]]; double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite; NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:downloadID,@"download",[NSNumber numberWithDouble:progress],@"progress",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"DownloadProgress" object:nil userInfo:userInfo]; }
所以当我访问self.downloads来获取正确的objectID时,我实际上是从与其创建的线程不同的线程访问NSMutableDictionary,我相信NSMutableDictionary不是线程安全的.那么什么是最好的解决方案,我可以使用这样的东西
session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
当声明会话时,将委托队列设置为mainQueue,这将导致在主线程上调用所有代理,但是如果可能,我想保留所有回调在后台线程上
解决方法
在您的示例中,这不是问题,因为您的字典被交给通知系统,并且不再被操作队列线程使用.线程安全性只是当一个对象可以同时从多个线程中被访问时才是一个问题.
如果你的dict将是一个iVar,你应该这样做:
像这样创建你自己的队列
myQueue = [[NSOperationQueue alloc] init]; // This creates basically a serial queue,since there is just on operation running at any time. [myQueue setMaxConcurrentOperationCount:1];
然后安排在这个队列上的每个Access到你的字典,例如:
[myQueue addOperationWithBlock:^ { // Access your dictionary }];
当然,您可以使用此队列作为您的URLSesson委托:
session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:myQueue];
由于这个队列被设置为串行队列,所以在后台总是只有一个线程访问dict.
当您使用dict信息计算某些内容时,请小心.您也必须在该队列上执行此操作.但是,您可以将计算结果放在任何其他队列/线程上,例如更新主线程上的UI.
[myQueue addOperationWithBlock:^ { // Calculate with your dictionary // Maybe the progress calcualtion NSString* progress = [self calculateProgress: iVarDict]; dispatch_async(dispatch_get_main_queue(),^ { // use progress to update UI }); }];
我认为发布通知您不必使用该模式,因为系统正确处理线程.但要保存,你应该检查一下.