我正在做大量的URL请求(大约60个小图片),我已经开始异步进行。我的代码添加了另一个图像(少量下载的东西),然后设置一个请求。
当请求完成后,我希望将“数据”放入最初为其添加的位置,但是,我无法看到如何将“imageLocation”传递给块,以便将图像存储在正确的位置位置。
我已经用下面的第3行替换了看起来有效,但我不是100%它是正确的(由于图像几乎相同,很难说)。我也在想,可以在声明块的位置传递“imageLocation”。
可以确认任何一个吗?
__ block int imageLocation = [allImages count] - 1;
// Add another image to MArray
[allImages addObject:[UIImage imageNamed:@"downloading.png"]];
imageLocation = [allImages count] - 1;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data != nil && error == nil)
{
//All Worked
[allImages replaceObjectAtIndex:imageLocation withObject:[UIImage imageWithData:data]];
}
else
{
// There was an error, alert the user
[allImages replaceObjectAtIndex:imageLocation withObject:[UIImage imageNamed:@"error.png"]];
}];
答案 0 :(得分:1)
处理异步方法很痛苦;)
在您的情况下,它保证完成块将在指定的队列上执行。但是,您需要确保队列的最大并发操作数为1,否则对共享资源的并发访问不安全。这是一场经典的比赛http://en.wikipedia.org/wiki/Race_condition。可以使用属性设置NSOperationQueue的最大并发操作。
通常,除非另有说明,否则完成处理程序可以在任何线程上执行。
使用名为“Promises”http://en.wikipedia.org/wiki/Promise_(programming)的概念时,处理异步方法会变得更加容易。基本上,“承诺”代表将在未来进行评估的结果 - 尽管如此,承诺本身立即可用。类似的概念被命名为“期货”或“延期”。
在GitHub上的Objective-C中有一个promise的实现:RXPromise。使用它时,您还可以从处理程序块到共享资源进行安全访问。实现如下:
-(RXPromise*) fetchImageFromURL:(NSString*)urlString queue:(NSOperationQueue*) queue
{
@autoreleasepool {
RXPromise* promise = [[RXPromise alloc] init];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data != nil) {
[promise fulfillWithValue:data];
}
else { // There was an error
[promise rejectWithReason:error];
};
}];
return promise;
}
}
然后叫它:
- (void) fetchImages {
...
for (NSUInteger index = 0; index < N; ++index)
{
NSString* urlString = ...
[self fetchImageFromURL:urlString, self.queue]
.then(^id(id data){
[self.allImages replaceObjectAtIndex:index withObject:[UIImage imageWithData:data]];
return @"OK";
},
^id(NSError* error) {
[self.allImages replaceObjectAtIndex:index withObject:[UIImage imageNamed:@"error.png"]];
return error;
});
}
}
答案 1 :(得分:1)
有几点想法:
如果你想下载60张图片,我不建议使用串行队列进行下载(例如,不要使用maxConcurrentOperationCount
1
的操作队列,而是使用并发队列。您将需要同步更新以确保您的代码是线程安全的(这可以通过将更新分派给串行队列(例如主队列)来轻松完成,以便最终更新模型),但我不会建议使用串行队列进行下载,因为这样会慢得多。
如果你想使用NSURLConnection
便捷方法,我会建议类似下面的并发操作请求方法(因为它在后台队列中,我正在使用{{1}而不是sendSynchronousRequest
),我假设您的图片网址有sendAsynchronousRequest
,NSArray
个imageURLs
个对象:
NSURL
一些旁白:首先,我使用的是操作队列而不是GCD并发队列,因为能够限制并发度是很重要的。其次,我添加了一个完成操作,因为我认为知道所有下载何时完成是有用的,但如果你不需要,那么代码显然更简单。第三,使用NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"all done %.1f", CFAbsoluteTimeGetCurrent() - start);
NSLog(@"allImages=%@", self.allImages);
}];
[imageURLs enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:&response error:&error];
if (!data) {
NSLog(@"%s sendSynchronousRequest error: %@", __FUNCTION__, error);
} else {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self.allImages replaceObjectAtIndex:idx withObject:image];
});
}
}
}];
[queue addOperation:operation];
[completionOperation addDependency:operation];
}];
[[NSOperationQueue mainQueue] addOperation:completionOperation];
的基准测试代码是不必要的,但如果您想使用CFAbsoluteTime
maxConcurrentOperationCount
与4
进行比较,则仅用于诊断目的。
比使用上面的1
便捷方法更好,您可能希望使用基于NSURLConnection
的网络请求。您可以自己编写或更好地使用经过验证的解决方案,例如AFNetworking。这可能看起来像:
NSOperation
因为AFNetworking将这些完成块调度回主队列,这解决了同步问题,同时仍然享受并发网络请求。
但这里的主要信息是基于CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"all done %.1f", CFAbsoluteTimeGetCurrent() - start);
NSLog(@"allImages=%@", self.allImages);
}];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFImageResponseSerializer serializer];
manager.operationQueue.maxConcurrentOperationCount = 4;
[imageURLs enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
NSOperation *operation = [manager GET:[url absoluteString] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.allImages replaceObjectAtIndex:idx withObject:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"%s image request error: %@", __FUNCTION__, error);
}];
[completionOperation addDependency:operation];
}];
[[NSOperationQueue mainQueue] addOperation:completionOperation];
的网络请求(或至少一个使用NSOperation
方法的网络请求)打开了额外的机会(例如,您可以取消所有这些网络请求如果必须,您可以获得进度更新等。)
坦率地说,我已经通过两个解决方案来说明如何有效地预先下载图像,我不得不指出这是一个本质上效率低下的过程。我可能会建议一个“懒惰”的图像加载过程,它以一种即时(a.k.a。“懒惰”)方式异步地请求图像。最简单的解决方案是使用NSURLConnectionDataDelegate
类别,例如AFNetworking或SDWebImage提供的类别。 (如果你已经将AFNetworking用于其他目的,我会使用AFNetworking,但我认为SDWebImage的UIImageView
类别更强一些。)这些不仅可以异步地无缝加载图像,还可以提供许多其他优势例如缓存,更有效的内存使用等。而且,它很简单:
UIImageView
关于有效执行网络请求的一些想法。我希望有所帮助。