ios – sendAsynchronousRequest使UI冻结
download
Images是一个按钮,每当我按下它时,一个微调器应该开始滚动,异步请求应该ping谷歌(以确保有连接)并且在收到响应后,我开始同步下载图像.
不知怎的,微调器不会去,似乎请求是同步而不是异步. - (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 FailednExtension: (%@) 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). (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |