Ios NSURLSession线程:跟踪多个后台下载
所以我在主线程上创建我的下载Ios NSURLSession线程:跟踪多个后台下载,ios,objective-c,multithreading,ios7,nsurlsession,Ios,Objective C,Multithreading,Ios7,Nsurlsession,所以我在主线程上创建我的下载 NSURLRequest *request = [NSURLRequest requestWithURL:download.URL]; NSURLSessionDownloadTask *downloadTask = [self.downloadSession downloadTaskWithRequest:request]; [downloadTask resume]; 并将与下载关联的NSManagedContextID添加到NSMutableDictiona
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;
}
session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
myQueue = [[NSOperationQueue alloc] init];
// This creates basically a serial queue, since there is just on operation running at any time.
[myQueue setMaxConcurrentOperationCount:1];
我的问题是代理回调似乎是在不同的线程上调用的
- (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不是线程安全的。那么什么是最好的解决方案,我可以用这样的方法
- (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;
}
session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
myQueue = [[NSOperationQueue alloc] init];
// This creates basically a serial queue, since there is just on operation running at any time.
[myQueue setMaxConcurrentOperationCount:1];
声明会话时,将委托队列设置为mainQueue,这会导致在主线程上调用所有委托,但我希望尽可能将所有回调保留在后台线程上,在您的示例中,这不是问题,因为您的字典已移交给通知系统,操作队列线程不再使用。只有当一个对象可能同时从多个线程访问时,线程安全才是一个问题 如果你的dict是iVar,你应该这样做: 像这样创建您自己的队列
- (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;
}
session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
myQueue = [[NSOperationQueue alloc] init];
// This creates basically a serial queue, since there is just on operation running at any time.
[myQueue setMaxConcurrentOperationCount:1];
然后在此队列上安排对词典的每次访问,例如:
[myQueue addOperationWithBlock:^
{
// Access your dictionary
}];
当然,对于您的URLSESON代表团,请使用此队列:
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
});
}];
我认为发布通知时不必使用这种模式,因为系统可以正确处理线程。但要保存,您应该选中此项。您可以使用GCD串行队列来确保只有一个委托同时执行 您可以将队列声明为类的实例变量,并在init方法中对其进行初始化,如下所示:
dispatch_queue_t delegateQueue;
在委托方法中,只需使其在该队列中执行:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
dispatch_sync(delegateQueue, ^{
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,您可以将它们保存在单独的线程中。在定义会话时,您是否尝试过简单地设置自己的线程?@Mundi如何处理
NSMutableArray
和NSObject
的模型,如本文中所述,这是一个稍微次优的模型,因为正如文档中所述,当delegateQueue设置为nil时,NSURLSession会创建一个串行队列。因此,在该队列上执行委托调用,然后代码立即切换到另一个队列。一个容易避免的队列(因此至少浪费了一个线程)。您可以将NSURLSERSESSION设置为main队列,但这也是不必要的队列时间。因为他会立即发布通知,所以将整个事件打包到主线程上会更明智。嗯,文档中说它不是:如何处理NSMutableArray
和NSObject
的模型,如中所述