不知怎的,微调器不会去,似乎请求是同步而不是异步.
- (IBAction)downloadImages:(id)sender { NSString *ping=@"http://www.google.com/"; GlobalVars *globals = [GlobalVars sharedInstance]; [self startSpinner:@"Please Wait."]; NSURL *url = [[NSURL alloc] initWithString:ping]; NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5.0]; [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response,NSData *data,NSError *error) { if (data) { for(int i=globals.farmerList.count-1; i>=0;i--) { //Definitions NSString * documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0]; //Get Image From URL NSString *urlString = [NSString stringWithFormat:@"https://myurl.com/%@",[[globals.farmerList objectAtIndex:i] objectForKey:@"Image"]]; UIImage * imageFromURL = [self getImageFromURL:urlString]; //Save Image to Directory [self saveImage:imageFromURL withFileName:[[globals.farmerList objectAtIndex:i] objectForKey:@"Image"] ofType:@"jpg" inDirectory:documentsDirectoryPath]; } [self stopSpinner]; } }]; }
微调器代码:
//show loading activity. - (void)startSpinner:(NSString *)message { // Purchasing Spinner. if (!connectingAlerts) { connectingAlerts = [[UIAlertView alloc] initWithTitle:NSLocalizedString(message,@"") message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:nil]; connectingAlerts.tag = (NSUInteger)300; [connectingAlerts show]; UIActivityIndicatorView *connectingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; connectingIndicator.frame = CGRectMake(139.0f-18.0f,50.0f,37.0f,37.0f); [connectingAlerts addSubview:connectingIndicator]; [connectingIndicator startAnimating]; } } //hide loading activity. - (void)stopSpinner { if (connectingAlerts) { [connectingAlerts dismissWithClickedButtonIndex:0 animated:YES]; connectingAlerts = nil; } // [self performSelector:@selector(showBadNews:) withObject:error afterDelay:0.1]; }
问:getImageFromURL代码
-(UIImage *) getImageFromURL:(NSString *)fileURL { UIImage * result; NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:fileURL]]; result = [UIImage imageWithData:data]; return result; } -(void) saveImage:(UIImage *)image withFileName:(NSString *)imageName ofType:(NSString *)extension inDirectory:(NSString *)directoryPath { if ([[extension lowercaseString] isEqualToString:@"png"]) { [UIImagePNGRepresentation(image) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@",imageName,@"png"]] options:NSAtomicWrite error:nil]; } else if ([[extension lowercaseString] isEqualToString:@"jpg"] || [[extension lowercaseString] isEqualToString:@"jpeg"]) { [UIImageJPEGRepresentation(image,1.0) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@",@"jpg"]] options:NSAtomicWrite error:nil]; } else { NSLog(@"Image Save Failed\nExtension: (%@) is not recognized,use (PNG/JPG)",extension); } }
解决方法
也就是说,你的按钮动作会调用这样的异步方法(它本身也变成“异步”):
- (IBAction)downloadImages:(id)sender { self.downloadImagesButton.enabled = NO; [self asyncLoadAndSaveImagesWithURLs:self.urls completion:^(id result,NSError* error){ if (error != nil) { NSLog(@"Error: %@",error); } dispatch_async(dispatch_get_main_queue(),^{ self.downloadImagesButton.enabled = YES; }; }]; }
因此,您的异步问题可以描述为:
给定URL列表,异步加载每个URL并以异步方式将它们保存到磁盘.加载并保存所有URL后,通过调用完成处理程序异步通知调用站点,并向其传递一组结果(对于每次下载和保存操作).
这是你的异步方法:
typedef void (^completion_t)(id result,NSError* error); - (void) asyncLoadAndSaveImagesWithURLs:(NSArray*)urls completion:(completion_t) completionHandler;
只有找到合适的异步模式才能正确解决异步问题.这涉及到异步问题的每个部分.
让我们从你的getImageFromURL方法开始.加载远程资源本质上是异步的,因此您的包装器方法最终也将是异步的:
typedef void (^completion_t)(id result,NSError* error); - (void) loadImageWithURL:(NSURL*)url completion:(completion_t)completionHandler;
我不确定该方法最终将如何实现.您可以使用NSURLConnection的异步方便类方法,第三方帮助工具或您自己的HTTPRequestOperation类.它没关系,但它必须是异步的,以实现一个理智的方法.
有目的地,您可以并且应该使您的saveImage方法同步.使此异步的原因是,此方法可能会同时调用,我们应该*序列化*磁盘绑定(I / O绑定)任务.这提高了系统资源的利用率,也使您的方法成为友好的系统公民.
这是异步版本:
typedef void (^completion_t)(id result,NSError* error); -(void) saveImage:(UIImage *)image fileName:(NSString *)fileName ofType:(NSString *)extension inDirectory:(NSString *)directoryPath completion:(completion_t)completionHandler;
为了序列化磁盘访问,我们可以使用专用队列disk_queue,我们假设它已经被self自己正确初始化为一个串行队列:
-(void) saveImage:(UIImage *)image fileName:(NSString *)fileName ofType:(NSString *)extension inDirectory:(NSString *)directoryPath completion:(completion_t)completionHandler { dispatch_async(self.disk_queue,^{ // save the image ... if (completionHandler) { completionHandler(result,nil); } }); }
现在,我们可以定义一个加载和保存图像的异步包装器:
typedef void (^completion_t)(id result,NSError* error); - (void) loadAndSaveImageWithURL:(NSURL*)url completion:(completion_t)completionHandler { [self loadImageWithURL:url completion:^(id image,NSError*error) { if (image) { [self saveImage:image fileName:fileName ofType:type inDirectory:directory completion:^(id result,NSError* error){ if (result) { if (completionHandler) { completionHandler(result,nil); } } else { DebugLog(@"Error: %@",error); if (completionHandler) { completionHandler(nil,error); } } }]; } else { if (completionHandler) { completionHandler(nil,error); } } }]; }
这个loadAndSaveImageWithURL方法实际上执行了两个异步任务的“延续”:
首先,异步加载图像.
那么,如果成功,则异步保存图像.
重要的是要注意这两个异步任务是按顺序处理的.
直到这里,这一切都应该是非常全面的,并且是直截了当的.现在,我们尝试以异步方式调用许多异步任务,这是棘手的部分.
异步循环
假设我们有一个URL列表.每个URL都应异步加载,并且当加载所有URL时,我们希望通知呼叫站点.
传统的for循环不适合实现这一点.但是想象一下,我们将使用这样的方法为NSArray创建一个类别:
NSArray的类别
- (void) forEachApplyTask:(task_t)transform completion:(completion_t)completionHandler;
这基本上是:对于数组中的每个对象,应用异步任务转换,并且当所有对象都已“转换”时,返回转换对象的列表.
注意:这个方法是异步的!
通过适当的“转换”功能,我们可以将其“翻译”为您的具体问题:
对于数组中的每个URL,应用异步任务loadAndSaveImageWithURL,并且在加载并保存所有URL后返回结果列表.
forEachApplyTask的实际实现:完成:可能看起来有点棘手,为简洁起见,我不想在这里发布完整的源代码.一种可行的方法需要大约40行代码.
我稍后会提供一个示例实现(在Gist上),但是让我们解释一下如何使用这个方法:
task_t是一个“块”,它接受一个输入参数(URL)并返回结果.
由于所有内容都必须异步处理,因此该块也是异步的,最终结果将通过完成块提供:
typedef void (^completion_t)(id result,NSError* error); typedef void (^task_t)(id input,completion_t completionHandler);
完成处理程序可以定义如下:
如果任务成功,则参数错误等于nil.否则,参数错误是NSError对象.也就是说,有效结果也可能是零.
我们可以很容易地包装我们的方法loadAndSaveImageWithURL:completion:并创建一个块:
task_t task = ^(id input,completion_t completionHandler) { [self loadAndSaveImageWithURL:input completion:completionHandler]; };
给定一系列URL:
self.urls = ...;
你的按钮动作可以实现如下:
- (IBAction)downloadImages:(id)sender { self.downloadImagesButton.enabled = NO; task_t task = ^(id input,completion_t completionHandler) { [self loadAndSaveImageWithURL:input completion:completionHandler]; }; [self.urls forEachApplyTask:task ^(id results,NSError*error){ self.downloadImagesButton.enabled = YES; if (error == nil) { ... // do something } else { // handle error } }]; }
再次注意,方法forEachApplyTask:completion:是一个异步方法,它立即返回.呼叫站点将通过完成处理程序得到通知.
downloadImages方法也是异步的,但是没有完成处理程序.此方法在启动时禁用按钮,并在异步操作完成后再次启用它.
这个forEachApplyTask方法的实现可以在这里找到:(https://gist.github.com/couchdeveloper/6155227).