2013-09-22 43 views
15

所以我创建主线程NSURLSession主题:跟踪多个后台下载

NSURLRequest *request = [NSURLRequest requestWithURL:download.URL]; 
NSURLSessionDownloadTask *downloadTask = [self.downloadSession downloadTaskWithRequest:request]; 
[downloadTask resume]; 

我的下载和添加具有下载到一个NSMutableDictionary相关的NSManagedContextID, 这样我就可以在后面的委托检索回调

[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这导致所有被称为主线程的代表,但我会喜欢在可能的情况下将所有回调保留在后台线程上

+0

您是否尝试过在定义会话时设置自己的线程? – Mundi

+0

@Mundi如何处理'NSMutableArray'和'NSObject'的模型,如[本教程?](http://www.appcoda.com/background-transfer-service-ios7/)所述 – Praveenkumar

回答

10

在您的示例中,这不是问题,因为您的字典已移交给通知系统,并且不会再被操作队列线程使用。当一个对象可能同时被多个线程访问时,线程安全只是一个问题。

如果你的字典是伊娃,你应该这样来做:

创建您自己的队列这样

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 
}]; 

当然并使用此队列为您URLSesson代表团:

session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:myQueue]; 

由于此队列设置为串行队列,因此在后台总是只有一个线程访问字典。

在计算字典信息时要小心。您也必须在该队列上执行此操作。但是,您可以将计算结果放在任何其他队列/线程上,例如更新主线程上的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 
    }); 
}]; 

我认为发布通知你不必使用该模式,因为系统正确处理线程。但为了节省,你应该检查一下。

+0

那么,文档这不是:[线程编程指南](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html) – sofacoder

+0

如何处理'NSMutableArray'和'NSObject'这个模型就像[本教程]中所解释的那样? – Praveenkumar

0

您可以使用GCD串行队列来确保只有一个代理正在同时执行。

可以声明队列为你的类的实例变量,并在init方法初始化它,就像这样:

dispatch_queue_t delegateQueue; 

...

delegateQueue = dispatch_queue_create("com.yourcompany.mydelegatequeue", 0x0); 

,并在你的委托方法,只需让它在这个队列中执行:

- (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]; 
}); 

} 

这样,虽然每个代表被称为i在它的线程中,一次只能访问self.downloads,并且可以将它们保存在单独的线程中。

+0

这是稍微不理想的,因为如文档中所述,NSURLSession在delegateQueue时创建一个串行队列设置为零。所以委托调用在该队列上执行,然后代码立即切换到另一个队列。一个队列(因此​​至少有一个线程被浪费)可以很容易地避免。你可以将NSURLSession设置为mainQueue,但是这又将是一个不需要队列的时间。 – sofacoder

+0

由于他立即发布通知,因此将整个事件包装在主线程中是更明智的做法。 – Andy