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];
快乐编码:)
- 如何在iOS中asynchronous下载图像?
- 与NSURLSessionasynchronous上传将无法正常工作,但同步NSURLConnection不会
- 如何asynchronous加载UITableViewcell图像,以便滚动不滞后
- swift:asynchronous任务+完成
- iOS – asynchronous图像下载
- 如何在asynchronous块内为dispatch_group_async调度dispatch_group_wait
- 如何使用Swift在iOS上同步运行两个函数
- NSOperationQueue内部的NSOperation中的asynchronouscallback从来没有被调用过
- 完成处理程序和返回值