sendAsynchronousRequest使UI冻结

downloadImages是一个button,每当我按下它,一个微调应该开始滚动,一个asynchronous请求应谷歌(以确保有一个连接),并收到响应后,我开始同步下载图像。

不知何故,微调不会去,似乎请求是同步,而不是asynchronous。

- (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:@"%@.%@", imageName, @"jpg"]] options:NSAtomicWrite error:nil]; } else { NSLog(@"Image Save Failed\nExtension: (%@) is not recognized, use (PNG/JPG)", extension); } } 

这是一个asynchronous的问题。 不同步是传染性的 。 这意味着,如果问题的任何一小部分是asynchronous的,则整个问题就变得asynchronous。

也就是说,你的button动作会调用这样的asynchronous方法(本身也变成“asynchronous的”):

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

所以,你的asynchronous问题可以这样描述:

给定一个URL列表, asynchronous加载每个URL并asynchronous保存到磁盘。 当所有的URL被加载和保存时,通过调用一个完成处理程序来传递一个结果数组(每次下载和保存操作), asynchronous地通知调用网站。

这是你的asynchronous方法:

 typedef void (^completion_t)(id result, NSError* error); - (void) asyncLoadAndSaveImagesWithURLs:(NSArray*)urls completion:(completion_t) completionHandler; 

asynchronous问题只有通过find合适的asynchronous模式才能正确解决。 这涉及到将问题的每个部分同步

让我们从你的getImageFromURL方法开始。 加载远程资源本质上是asynchronous的,所以你的包装方法最终也是asynchronous的

 typedef void (^completion_t)(id result, NSError* error); - (void) loadImageWithURL:(NSURL*)url completion:(completion_t)completionHandler; 

我不确定这种方法最终将如何实施。 您可以使用NSURLConnection的asynchronous方便类方法,第三方帮助程序工具或您自己的HTTPRequestOperation类。 这并不重要,但它必须是asynchronous的,以实现一个理智的方法。

saveImage ,你可以应该让你的saveImage方法也是asynchronous的。 做这个asynchronous的原因是,这个方法可能会被并发调用,我们应该 *序列化*磁盘绑定(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 ,我们假设它已经被自己正确初始化为一个串行队列:

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

现在,我们可以定义一个加载和保存图像的asynchronous包装:

 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方法实际上执行两个asynchronous任务的“继续”:

首先,asynchronous加载图像。 那么,如果这是成功的,asynchronous保存图像。

需要注意的是,这两个asynchronous任务是按顺序处理的。

直到这里,这一切都应该是相当全面和直截了当的。 现在我们试图以asynchronous方式调用一些asynchronous任务。

asynchronous循环

假设我们有一个URL列表。 每个URL都应该被asynchronous加载,当所有的URL都被加载的时候,我们希望通知这个呼叫站点。

传统的for循环不适合完成这个。 但想象一下,我们将有一个NSArray类别与这样的方法:

NSArray类别

 - (void) forEachApplyTask:(task_t)transform completion:(completion_t)completionHandler; 

这基本上读取:对于数组中的每个对象,应用asynchronous任务transform并当所有对象已被“转换”返回转换对象的列表。

注意:这个方法是asynchronous的!

通过适当的“转换”function,我们可以将其“转化”为您的具体问题:

对于数组中的每个URL,应用asynchronous任务loadAndSaveImageWithURL并在所有URLS被加载并保存后返回结果列表。

forEachApplyTask:completion:的实际实现可能会有点棘手,为了简洁,我不想在这里发布完整的源代码。 一个可行的方法需要大约40行代码。

稍后我会提供一个示例实现(在Gist上),但是让我们解释一下如何使用这个方法:

task_t是一个“块”,它接受一个input参数(URL)并返回一个结果。 由于一切都必须asynchronous处理,所以这个块也是asynchronous的最终结果将通过一个完成块提供:

 typedef void (^completion_t)(id result, NSError* error); typedef void (^task_t)(id input, completion_t completionHandler); 

完成处理程序可以定义如下:

如果任务成功,则参数错误 nil 。 否则,参数错误是一个NSError对象。 也就是说,一个有效的结果也可能是nil

我们可以很轻松地包装我们的方法loadAndSaveImageWithURL:completion:并创build一个块:

 task_t task = ^(id input, completion_t completionHandler) { [self loadAndSaveImageWithURL:input completion:completionHandler]; }; 

给定一组URL:

 self.urls = ...; 

你的button动作可以实现如下:

 - (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:是一个asynchronous方法,它立即返回。 呼叫地点将通过完成处理程序通知。

downloadImages方法也是asynchronous的,尽pipe没有完成处理程序。 该方法在启动时禁用该button,并在asynchronous操作完成时再次启用该button。

这个forEachApplyTask方法的实现可以在这里find:( https://gist.github.com/couchdeveloper/6155227 )。

这是因为你正在创build一个asynchronous操作,然后通过使用[NSOperationQueue mainQueue];告诉它在主线程上执行[NSOperationQueue mainQueue];

相反,创build一个NSOpeartionQueue的新实例,并将其作为parameter passing。

 NSOperationQueue *myQueue = [[NSOperationQueue alloc] init]; 

从你的代码,我可以理解的是它不是由于assyncronous调用来加载url。 但是下面的代码可能很重。

对于同步图像加载,请尝试https://github.com/rs/SDWebImage

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

快乐编码:)