如何在UIImageView中异步加载图像?

时间:2013-10-04 10:37:20

标签: ios objective-c uiimageview grand-central-dispatch

我有一个带有UIImageView子视图的UIView。我需要在UIImageView中加载图像而不阻止UI。阻止调用似乎是:UIImage imageNamed:。以下是我认为解决了这个问题:

-(void)updateImageViewContent {
    dispatch_async(
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        UIImage * img = [UIImage imageNamed:@"background.jpg"];
        dispatch_sync(dispatch_get_main_queue(), ^{
            [[self imageView] setImage:img];
        });
    });
}

图像很小(150x100)。

但是,加载图像时仍然会阻止UI。我错过了什么?

以下是一个展示此行为的小代码示例:

基于UIImageView创建一个新类,将其用户交互设置为YES,在UIView中添加两个实例,并实现其touchesBegan方法,如下所示:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if (self.tag == 1) {
        self.backgroundColor= [UIColor redColor];
    }

    else {
    dispatch_async(dispatch_get_main_queue(), ^{
    [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
  });
    [UIView animateWithDuration:0.25 animations:
        ^(){[self setFrame:CGRectInset(self.frame, 50, 50)];}];
    }
}

将标记1分配给其中一个imageView。

从加载图像的视图开始,几乎同时点击两个视图时会发生什么? UI是否因为等待[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];返回而被阻止?如果是这样,我该如何异步执行此操作?

这是一个带有ipmcc代码的project on github

使用长按然后拖动在黑色方块周围绘制一个矩形。据我理解他的答案,理论上白色选择矩形不应该在第一次加载图像时被阻挡,但实际上是。

项目中包含两个图像(一个小图片:woodenTile.jpg和一个较大图片:bois.jpg)。结果与两者相同。

图像格式

我真的不明白这与第一次加载图像时UI被阻止的问题有什么关系,但是PNG图像在不阻挡UI的情况下进行解码,而JPG图像会阻止UI

事件的年表

step 0 step 1

阻止UI从这里开始..

step 2

..并在此结束。

step 3

AFNetworking解决方案

    NSURL * url =  [ [NSBundle mainBundle]URLForResource:@"bois" withExtension:@"jpg"];
    NSURLRequest * request = [NSURLRequest requestWithURL:url];
    [self.imageView setImageWithURLRequest:request
                          placeholderImage:[UIImage imageNamed:@"placeholder.png"]
                                   success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
                                       NSLog(@"success: %@", NSStringFromCGSize([image size]));
                                   } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
                                       NSLog(@"failure: %@", response);
                                   }];

// this code works. Used to test that url is valid. But it's blocking the UI as expected.
if (false)       
if (url) {
        [self.imageView setImage: [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]]; }

大部分时间,它会记录:success: {512, 512}

它还偶尔记录:success: {0, 0}

有时:failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...

但图像永远不会改变。

10 个答案:

答案 0 :(得分:53)

问题是UIImage在第一次实际使用/绘制图像之前实际上并未读取和解码图像。要在后台线程上强制执行此工作,您必须在执行主线程-setImage:之前在后台线程上使用/绘制图像。这对我有用:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    UIImage * img = [UIImage imageNamed:@"background.jpg"];

    // Make a trivial (1x1) graphics context, and draw the image into it
    UIGraphicsBeginImageContext(CGSizeMake(1,1));
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), [img CGImage]);
    UIGraphicsEndImageContext();

    // Now the image will have been loaded and decoded and is ready to rock for the main thread
    dispatch_sync(dispatch_get_main_queue(), ^{
        [[self imageView] setImage: img];
    });
});

编辑:用户界面没有阻止。您已经专门设置它使用UILongPressGestureRecognizer,默认情况下等待半秒钟才能执行任何操作。主线程仍在处理事件,但在GR超时之前不会发生任何事情。如果你这样做:

    longpress.minimumPressDuration = 0.01;

...你会发现它变得更加快捷。图像加载不是问题所在。

编辑2:我看过代码,发布到github,在iPad 2上运行,我根本没有得到你描述的打嗝。事实上,它非常顺利。以下是在CoreAnimation工具中运行代码的屏幕截图:

enter image description here

正如您在顶部图表中看到的那样,FPS最高可达~60FPS并在整个手势中保持不变。在底部的图表中,您可以看到大约16s的blip,这是图像首次加载的位置,但您可以看到帧速率没有下降。仅从视觉检查,我看到选择层相交,并且在第一个交叉点和图像外观之间存在一个小但可观察到的延迟。据我所知,后台加载代码正在按预期工作。

我希望我能帮助你更多,但我只是没有看到问题。

答案 1 :(得分:5)

您可以使用AFNetworking 库,其中导入类别

“的UIImageView + AFNetworking.m” 并使用如下方法:

[YourImageView setImageWithURL:[NSURL URLWithString:@"http://image_to_download_from_serrver.jpg"] 
      placeholderImage:[UIImage imageNamed:@"static_local_image.png"]
               success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
                  //ON success perform 
               }
               failure:NULL];

希望这会有所帮助。

答案 2 :(得分:4)

我的应用程序遇到了一个非常类似的问题,我不得不下载大量图片,并且我的UI不断更新。以下是解决我的问题的简单教程链接:

NSOperations & NSOperationQueues Tutorial

答案 3 :(得分:3)

这是好方法:

-(void)updateImageViewContent {
    dispatch_async(dispatch_get_main_queue(), ^{

        UIImage * img = [UIImage imageNamed:@"background.jpg"];
        [[self imageView] setImage:img];
    });
}

答案 4 :(得分:2)

为什么不使用像AsyncImageView这样的第三方库?使用它,您所要做的就是声明您的AsyncImageView对象并传递您要加载的url或图像。在图像加载期间将显示活动指示器,并且不会阻止UI。

答案 5 :(得分:1)

- (void)touchesBegan:在主线程中调用。通过调用dispatch_async(dispatch_get_main_queue),您只需将块放入队列中。当队列准备就绪时,该块将由GCD处理(即系统结束并处理您的触摸)。这就是为什么你不能看到你的woodenTile加载并分配给self.image,直到你释放你的手指并让GCD处理所有在主队列中排队的块。

更换:

    dispatch_async(dispatch_get_main_queue(), ^{
    [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
  });

by:

[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];

应解决您的问题...至少对于展示它的代码。

答案 6 :(得分:0)

考虑使用SDWebImage:它不仅可以在后台下载和缓存图像,还可以加载和渲染图像。

我在桌面视图中使用了它,结果很好,即使在下载后也会出现加载速度很慢的大图像。

答案 7 :(得分:0)

https://github.com/nicklockwood/FXImageView

这是一个可以处理后台加载的图像视图。

用法

FXImageView *imageView = [[FXImageView alloc] initWithFrame:CGRectMake(0, 0, 100.0f, 150.0f)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.asynchronous = YES;

//show placeholder
imageView.processedImage = [UIImage imageNamed:@"placeholder.png"];

//set image with URL. FXImageView will then download and process the image
[imageView setImageWithContentsOfURL:url];

要获取文件的网址,您可能会发现以下内容:

Getting bundle file references / paths at app launch

答案 8 :(得分:0)

在应用程序中使用AFNetwork时,您无需使用任何块来加载图像,因为AFNetwork为其提供了解决方案。如下:

#import "UIImageView+AFNetworking.h"

并且

   Use **setImageWithURL** function of AFNetwork....

由于

答案 9 :(得分:0)

我实现它的一种方法是以下:(虽然我不知道它是否是最好的一种)

首先,我使用串行异步或并行队列

创建队列
queue = dispatch_queue_create("com.myapp.imageProcessingQueue", DISPATCH_QUEUE_SERIAL);**

or

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);

**

您可能会更好地满足您的需求。

然后:

 dispatch_async( queue, ^{



            // Load UImage from URL
            // by using ImageWithContentsOfUrl or 

            UIImage *imagename = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];

            // Then to set the image it must be done on the main thread
            dispatch_sync( dispatch_get_main_queue(), ^{
                [page_cover setImage: imagename];
                imagename = nil;
            });

        });