Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/105.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/objective-c/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios NSURLSession线程:跟踪多个后台下载_Ios_Objective C_Multithreading_Ios7_Nsurlsession - Fatal编程技术网

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
的模型,如中所述