sendAsynchronousRequest使得UI冻结 [英] sendAsynchronousRequest makes UI freezes

查看:200
本文介绍了sendAsynchronousRequest使得UI冻结的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

downloadImages是一个按钮,每当我在上面preSS,一个微调应该开始滚动,一个异步请求应该执行ping谷歌(以确保有一个连接),并收到响应后,我开始共时下载图像。

不知何故,微调不会去,似乎如果请求是同步和异步不作为。

   - (IBAction为)downloadImages:(ID)发送{    * NSString的平= @http://www.google.com/    GlobalVars *全局= [GlobalVars sharedInstance];
    [个体经营startSpinner:@请稍候。];
    NSURL * URL = [[NSURL页头] initWithString:平]。
    *的NSURLRequest请求= [的NSURLRequest requestWithURL:URL的CachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5.0];
    [NSURLConnection的sendAsynchronousRequest:请求队列:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *响应,NSData的*数据,NSError *错误){
        如果(数据){
            的for(int i = globals.farmerList.count-1; I> = 0;我 - )
            {
            //定义
            的NSString * documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,是)objectAtIndex:0];            //获取图像从URL
                * NSString的urlString = [的NSString stringWithFormat:@https://myurl.com/%@,[[globals.farmerList objectAtIndex:我] objectForKey:@图像]];
            *的UIImage = imageFromURL [个体经营getImageFromURL:urlString];            //保存图像到目录
            [个体经营saveImage:imageFromURL withFileName:[globals.farmerList objectAtIndex:我] objectForKey:@图片] ofType:@JPGinDirectory:documentsDirectoryPath];
            }
            [个体经营stopSpinner]        }
    }];
}

微调器code:

  //装秀活动。
- (无效)startSpinner:(* NSString的)消息{
    //采购微调。
    如果(!connectingAlerts){
        connectingAlerts = [[UIAlertView中页头] initWithTitle:NSLocalizedString(消息,@)
                                                     消息:无
                                                    委托:自我
                                           cancelButtonTitle:无
                                           otherButtonTitles:无];
        connectingAlerts.tag =(NSUInteger)300;
        [connectingAlerts显示]        UIActivityIndi​​catorView * connectingIndicator = [[UIActivityIndi​​catorView页头] initWithActivityIndi​​catorStyle:UIActivityIndi​​catorViewStyleWhiteLarge];
        connectingIndicator.frame = CGRectMake(139.0f,18.0f,50.0f,37.0f,37.0f);
        [connectingAlerts addSubview:connectingIndicator];
        [connectingIndicator startAnimating]    }
}
//隐藏加载活动。
- (无效){stopSpinner
    如果(connectingAlerts){
        [connectingAlerts dismissWithClickedButtonIndex:0动画:是];
        connectingAlerts =零;
    }
    // [自performSelector:@选择(showBadNews :) withObject:错误afterDelay:0.1];
}

至于问:getImageFromURL code

   - (*的UIImage)getImageFromURL:(* NSString的)fileURL {
    * UIImage的结果;    NSData的*数据= [NSData的dataWithContentsOfURL:[NSURL URLWithString:fileURL]];
    结果= [UIImage的imageWithData:数据]    返回结果;
}
- (无效)saveImage:(*的UIImage)图像withFileName:(* NSString的)imageName ofType:(* NSString的)扩展inDirectory:(* NSString的)目录路径{
    如果([延伸lowercaseString] isEqualToString:@PNG]){
        [UIImagePNGRe presentation(图片)将writeToFile:[目录路径stringByAppendingPathComponent:[的NSString stringWithFormat:@,imageName,@PNG]]选项:NSAtomicWrite错误:%@%@。无]。
    }否则如果([延伸lowercaseString] isEqualToString:@JPG] || [延伸lowercaseString] isEqualToString:@JPEG]){
        [UIImageJPEGRe presentation(图像,1.0)将writeToFile:[目录路径stringByAppendingPathComponent:[的NSString stringWithFormat:@%@%@,imageName,@JPG]选项:NSAtomicWrite错误:无];
    }其他{
        的NSLog(@图像保存失败\\ nExtension:(%@)无法识别,使用(PNG / JPG),扩展);
    }
}


解决方案

这是一个异步的问题。异步是的感染的。这意味着,如果的任何的问题的一小部分是异步的,的的问题变得异步的。

这就是你的按钮操作将调用这样的异步方法(和本身成为异步以及):

   - (IBAction为)downloadImages:(ID)发送者
{
    self.downloadImagesButton.enabled = NO;
    [个体经营asyncLoadAndSaveImagesWithURLs:self.urls完成:^(ID结果,NSError *错误){
        如果(错误!=无){
            的NSLog(@错误:%@,错误);
        }
        dispatch_async(dispatch_get_main_queue(),^ {
            self.downloadImagesButton.enabled = YES;
        };
    }];
}

所以,你的异步的问题可以描述为这样的:

由于的URL列表,的异步的加载每个URL和异步的将它们保存到磁盘。当所有的URL加载和保存的异步的调用完成处理程序传递给它的结果数组(每个下载和保存操作)通知调用现场。

这是你的异步方式:

 的typedef无效(^ completion_t)(ID结果,NSError *误差); - (无效)asyncLoadAndSaveImagesWithURLs:(NSArray的*)的网址
                             完成:(completion_t)completionHandler;

异步的问题只能由找到一个合适的异步的格局妥善解决。这涉及到的 asynchronize 每个问题的一部分。

让我们开始与你的 getImageFromURL 方法。加载远程资源本质上是异步的,所以你的包装方法的最终将是异步的还有:

 的typedef无效(^ completion_t)(ID结果,NSError *误差); - (无效)loadImageWithURL:(NSURL *)网址完成:(completion_t)completionHandler;

我不予以定义怎么说的方法最终将实现。您可以使用NSURLConnection的的异步方便类方法,第三方辅助工具或您自己的 HTT prequestOperation 类。不要紧,但必须是异步的实现的理智的办法。

有意,你的可以的和的的使你的 saveImage 方法异步为好。之所以提出这个异步是,这种方法的可能的会被调用的同时的,而我们的的*连载*磁盘绑定的(I / O约束)的任务。这的提高了的系统资源的利用,也使你的方法一个友好的系统公民。

下面是异步版本:

 的typedef无效(^ completion_t)(ID结果,NSError *误差); - (无效)saveImage:(*的UIImage)图像文件名:(* NSString的)文件名ofType:(* NSString的)扩展
                                inDirectory:(* NSString的)目录路径
                                 完成:(completion_t)completionHandler;

为了连载接盘,我们可以使用一个专用的队列 disk_queue 在这里我们假设它已通过自正确初始化作为串行队列

   - (无效)saveImage:(*的UIImage)图像文件名:(* NSString的)文件名ofType:(* NSString的)扩展
                                inDirectory:(* NSString的)目录路径
                                 完成:(completion_t)completionHandler
{
    dispatch_async(self.disk_queue,^ {
        //保存图片
        ...
        如果(completionHandler){
            completionHandler(结果,无);
        }
    });
}

现在,我们可以定义的异步的封装程序加载和保存图像:

 的typedef无效(^ completion_t)(ID结果,NSError *误差); - (无效)loadAndSaveImageWithURL:(NSURL *)网址完成:(completion_t)completionHandler
{
    [个体经营loadImageWithURL:URL完成:^(ID形象,NSError *错误){
        如果(图片){
            [自我saveImage:图像文件名:文件名ofType:类型inDirectory:目录完成:^(ID结果,NSError *错误){
                如果(结果){
                    如果(completionHandler){
                        completionHandler(结果,无);
                    }
                }
                其他{
                    DEBUGLOG(@错误:%@,错误);
                    如果(completionHandler){
                        completionHandler(零,错误);
                    }
                }
            }];
        }
        其他{
            如果(completionHandler){
                completionHandler(零,错误);
            }
        }
    }];
}

loadAndSaveImageWithURL 方法实际执行的延续的两个的异步任务:

首先,异步加载。
  然后,如果成功,异步保存图像。

要注意这两个异步任务是很重要的连续的处理。

直到这里,这一切都应该是很COM prehensive并直截了当。现在最棘手的部分如下,我们尝试调用的数量的的以异步方式异步任务。

异步循环

假设,我们有一个网址列表。每个URL应异步加载,而当所有的URL加载,我们希望收到呼叫站点。

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

为NSArray的类别

   - (无效)forEachApplyTask:(task_t)转换完成:(completion_t)completionHandler;

这主要内容如下:数组中的每个对象,应用异步任务变换键,当所有的对象都被改造返回改造对象的列表<。 / p>

请注意:此方法是异步

通过适当的转换功能,我们可以翻译给你的特定问题:

有关阵列中的每个网址,应用异步任务 loadAndSaveImageWithURL ,当所有的URL已经被载入和保存返回结果的列表。

forEachApplyTask的实际实现:完成:可能会出现一个有点棘手,并为简单起见,我不想在这里发表完整的源代码。一种可行的方法大约需要40 $ C $行C。

我将在后面(在GIST)提供了一个示例实现,但让我们解释一下如何使用这个方法:

task_t 是一个块这需要一个输入参数(URL),并返回结果。
由于的所有的必须是异步处理,此块的异步的为好,和的最后的结果将通过完成模块来提供:

 的typedef无效(^ completion_t)(ID结果,NSError *误差);无效的typedef(^ task_t)(ID输入,completion_t completionHandler);

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

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

我们可以很容易地的我们的方法 loadAndSaveImageWithURL:完成:,并创建一个块:

  task_t任务= ^(ID输入,completion_t completionHandler){
    [个体经营loadAndSaveImageWithURL:输入完成:completionHandler];
};

由于的URL的数组:

  self.urls = ...;

您的按钮操作可以实现如下:

   - (IBAction为)downloadImages:(ID)发送者
{
    self.downloadImagesButton.enabled = NO;    task_t任务= ^(ID输入,completion_t completionHandler){
        [个体经营loadAndSaveImageWithURL:输入完成:completionHandler];
    };    [self.urls forEachApplyTask:任务^(ID结果,NSError *错误){
        self.downloadImagesButton.enabled = YES;
        如果(错误==无){
            ... // 做一点事
        }
        其他{
            //处理错误
        }
    }];
}

再次注意方法 forEachApplyTask:完成:异步的方法,它立即返回。呼叫站点将通过完成处理程序的通知。

downloadImages 方法是异步的,以及,没有完成处理程序,但。当它启动,并再次使其当异步操作已经完成此方法禁用按钮。

此的实施 forEachApplyTask 方法可以在这里找到:(的 https://gist.github.com/couchdeveloper/6155227 )。

downloadImages is a button and whenever I press on it, a spinner should start rolling, an async request should ping Google (to make sure there is a connection) and after a response is received, I start to synchronically downloading images.

Somehow the spinner won't go and it seems as if the request is sync and not async.

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

        }
    }];
}

The spinner code:

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

As asked: the getImageFromURL code

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

解决方案

This is an asynchronous problem. Asynchronism is infectious. That means, if any small part of the problem is asynchronous, the whole problem becomes asynchronous.

That is, your button action would invoke an asynchronous method like this (and itself becomes "asynchronous" as well):

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

So, your asynchronous problem can be described as this:

Given a list of URLs, asynchronously load each URL and asynchronously save them to disk. When all URLs are loaded and saved, asynchronously notify the call-site by calling a completion handler passing it an array of results (for each download and save operation).

This is your asynchronous method:

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

- (void) asyncLoadAndSaveImagesWithURLs:(NSArray*)urls 
                             completion:(completion_t) completionHandler;

Asynchronous problems can be solved properly only by finding a suitable asynchronous pattern. This involves to asynchronize every part of the problem.

Lets start with your getImageFromURL method. Loading a remote resource is inherently asynchronous, so your wrapper method ultimately will be asynchronous as well:

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

- (void) loadImageWithURL:(NSURL*)url completion:(completion_t)completionHandler;

I leave it undefined how that method will be eventually implemented. You may use NSURLConnection's asynchronous convenient class method, a third party helper tool or your own HTTPRequestOperation class. It doesn't matter but it MUST be asynchronous for achieving a sane approach.

Purposefully, you can and should make your saveImage method asynchronous as well. The reason for making this asynchronous is, that this method possibly will get invoked concurrently, and we should *serialize* disk bound (I/O bound) tasks. This improves utilization of system resources and also makes your approach a friendly system citizen.

Here is the asynchronized version:

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; 

In order to serialize disk access, we can use a dedicated queue disk_queue where we assume it has been properly initialized as a serial queue by 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);
        }
    });
}

Now, we can define an asynchronous wrapper which loads and saves the image:

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

This loadAndSaveImageWithURL method actually performs a "continuation" of two asynchronous tasks:

First, asynchronously load the image. THEN, if that was successful, asynchronously save the image.

It's important to notice that these two asynchronous tasks are sequentially processed.

Up until here, this all should be quite comprehensive and be straight forward. The tricky part follows now where we try to invoke a number of asynchronous tasks in an asynchronous manner.

Asynchronous Loop

Suppose, we have a list of URLs. Each URL shall be loaded asynchronously, and when all URLs are loaded we want the call-site to be notified.

The traditional for loop is not that appropriate for accomplishing this. But imagine we would have a Category for a NSArray with a method like this:

Category for NSArray

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

This basically reads: for each object in the array, apply the asynchronous task transform and when all objects have been "transformed" return a list of the transformed objects.

Note: this method is asynchronous!

With the appropriate "transform" function, we can "translate" this to your specific problem:

For each URL in the array, apply the asynchronous task loadAndSaveImageWithURL and when all URLS have been loaded and saved return a list of the results.

The actual implementation of the forEachApplyTask:completion: may appear a bit tricky and for brevity I don't want to post the complete source here. A viable approach requires about 40 lines of code.

I'll provide an example implementation later (on Gist), but lets explain how this method can be used:

The task_t is a "block" which takes one input parameter (the URL) and returns a result. Since everything must be treated asynchronously, this block is asynchronous as well, and the eventual result will be provided via a completion block:

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

typedef void (^task_t)(id input, completion_t completionHandler);

The completion handler may be defined as follows:

If the tasks succeeds, parameter error equals nil. Otherwise, parameter error is an NSError object. That is, a valid result may also be nil.

We can quite easily wrap our method loadAndSaveImageWithURL:completion: and create a block:

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

Given an array of URLs:

self.urls = ...;

your button action can be implemented as follows:

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

Again, notice that method forEachApplyTask:completion: is an asynchronous method, which returns immediately. The call-site will be notified via the completion handler.

The downloadImages method is asynchronous as well, there is no completion handler though. This method disables the button when it starts and enables it again when the asynchronous operation has been completed.

The implementation of this forEachApplyTask method can be found here: (https://gist.github.com/couchdeveloper/6155227).

这篇关于sendAsynchronousRequest使得UI冻结的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆