downloadImages是一个按钮,每当我按下它时,旋转器就会开始滚动,异步请求应该会平Google (以确保有连接),并且在收到响应后,我开始同步下载图像。
不知何故,旋转器不会消失,似乎请求是同步的,而不是异步的。
- (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);
}
}发布于 2013-11-22 10:38:19
这是一个异步问题。异步是有传染性的。这意味着,如果问题的任何一小部分是异步的,那么整个问题就会变成异步的。
也就是说,您的按钮操作将调用这样的异步方法(并且它本身也会变成“异步”):
- (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;这基本上是这样的:对于数组中的每个对象,应用异步任务transform,当所有对象都被“转换”时,返回转换对象的列表。
注意:这个方法是异步的!
通过适当的“转换”功能,我们可以将其“转换”到您的特定问题上:
对于数组中的每个URL,应用异步任务loadAndSaveImageWithURL,当加载和保存所有URL时,返回结果列表。
forEachApplyTask:completion:的实际实现可能看起来有点棘手,为了简洁起见,我不想在这里发布完整的源代码。一个可行的方法需要大约40行代码。
稍后我将提供一个示例实现(关于Gist),但让我们解释如何使用该方法:
task_t是一个“块”,它接受一个输入参数( URL)并返回一个结果。由于必须异步处理所有东西,所以这个块也是异步的,最终结果将通过一个完成块提供:
typedef void (^completion_t)(id result, NSError* error);
typedef void (^task_t)(id input, completion_t completionHandler);完成处理程序可以定义如下:
如果任务成功,则参数错误等于nil。否则,参数错误是NSError对象。也就是说,一个有效的结果也可能是nil。
我们可以很容易地包装我们的方法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)。
发布于 2013-11-21 14:48:42
这是因为您正在创建一个异步操作,然后告诉它使用[NSOperationQueue mainQueue];在主线程上执行。
相反,创建一个新的NSOpeartionQueue实例并将其作为参数传递。
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];发布于 2013-11-21 14:53:12
从您的代码中,我可以理解的是,它并不是由于对加载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];快乐编码:)
https://stackoverflow.com/questions/20031267
复制相似问题