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

Ios 如何在UIImageView中异步加载图像?,ios,objective-c,uiimageview,grand-central-dispatch,Ios,Objective C,Uiimageview,Grand Central Dispatch,我有一个UIView和一个UIImageView子视图。我需要在不阻塞UI的情况下在UIImageView中加载图像。阻塞调用似乎是:UIImage imageNamed:。以下是我认为解决这个问题的方法: -(void)updateImageViewContent { dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ UIImage *

我有一个UIView和一个UIImageView子视图。我需要在不阻塞UI的情况下在UIImageView中加载图像。阻塞调用似乎是:
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中添加两个实例,并实现其touchesBegind方法,如下所示:

-(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-imagename:@“woodenTile.jpg]”而被阻止返回?如果是这样,我如何异步执行此操作

下面是一个ipmcc代码的示例

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

项目中包含两个图像(一个小的:woodenTile.jpg,一个大的:bois.jpg)。两者的结果是相同的

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

事件年表

UI的阻塞从这里开始

。。到此为止

AFN网络解决方案 大多数时候,它会记录:
success:{512,512}

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

有时:
失败:{URL:file:///var/mobile/Appl...

但是图像永远不会改变。

这是一个好方法:

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

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

为什么不像这样使用第三方库呢?使用它,您只需声明AsyncImageView对象并传递要加载的url或图像。图像加载过程中会显示一个活动指示器,没有任何东西会阻止UI。

问题是,
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];
    });
});
编辑:UI没有阻塞。您专门将其设置为使用
UILongPressGestureRecognitizer
,默认情况下,该识别器在执行任何操作前等待半秒。主线程仍在处理事件,但在GR超时之前不会发生任何事情。如果执行此操作:

    longpress.minimumPressDuration = 0.01;
…您会注意到它变得更加快速。图像加载不是问题所在

编辑2:我已经看过了发布到github的代码,它在iPad 2上运行,我根本不明白你所描述的问题。事实上,它非常流畅。下面是在CoreAnimation instrument中运行代码的截图:

正如你在上图中看到的,FPS一直上升到~60FPS,并在整个手势过程中保持不变。在下图中,你可以看到16秒左右的光点,这是图像第一次加载的位置,但你可以看到帧速率没有下降。仅从目测中,我看到选择层相交,并且有一个小的变化l、 但是在第一个交叉点和图像出现之间有明显的延迟。据我所知,背景加载代码正在按预期工作

我希望我能为您提供更多帮助,但我看不出问题所在。

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

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

希望这能有所帮助。

我的应用程序也遇到了一个非常类似的问题,我不得不下载大量图像,同时我的UI也在不断更新。下面是解决我问题的简单教程链接:

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

我在tableview中使用过它,效果很好,它的大图像即使在下载后也很难加载。

这是一个可以处理背景加载的图像视图

用法

要获取文件的URL,您可能会发现以下有趣的内容:


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

#import "UIImageView+AFNetworking.h"


谢谢

我实现它的一个方法是:(尽管我不知道它是否是最好的)

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

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

        });
-(void)touchsbegind:在主线程中调用。通过调用dispatch_async(dispatch_get_main_queue),您只需将块放入队列中。当队列准备就绪时(即系统已结束处理您的触摸),GCD将处理此块。这就是为什么在释放手指并让GCD处理已在主队列中排队的所有块之前,您无法看到woodenTile加载并分配给self.image的原因

替换:

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


应该可以解决您的问题…至少对于展示它的代码来说是这样。

您没有遗漏任何东西。UI更改必须始终同步。这是您能做的最好的事情。图像有多大?您可能会发现,阻止调用不是加载图像,而是在屏幕上显示图像视图时呈现图像内容。H你有没有试过对应用程序进行分析,看看瓶颈在哪里?你只是盲目地假设吗
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;
            });

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