25

我有一个带有 UIImageView 子视图的 UIView。我需要在不阻塞 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 中添加两个实例,并实现其 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"]];返回而被阻止?如果是这样,我该如何异步执行此操作?

这是 github上的一个项目,带有ipmcc代码

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

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

图像格式

我真的不明白这与我在第一次加载图像时仍然遇到 UI 被阻塞的问题有什么关系,但是 PNG 图像在不阻塞 UI 的情况下解码,而 JPG 图像确实阻塞了 UI。

事件年表

步骤 0 步骤1

UI的阻塞从这里开始..

第2步

.. 到此结束。

第 3 步

AF网络解决方案

    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...

但图像从未改变。

4

11 回答 11

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

编辑:用户界面没有阻塞。您已将其专门设置为使用UILongPressGestureRecognizerwhich 在执行任何操作之前默认等待半秒。主线程仍在处理事件,但在 GR 超时之前什么都不会发生。如果你这样做:

    longpress.minimumPressDuration = 0.01;

...您会注意到它变得更加活泼。图像加载不是这里的问题。

编辑 2:我查看了发布到 github 上的代码,该代码在 iPad 2 上运行,但我根本没有得到您所描述的问题。事实上,它非常顺利。这是在 CoreAnimation 工具中运行代码的屏幕截图:

在此处输入图像描述

正如您在上图中所见,FPS 最高可达 ~60FPS,并在整个手势过程中保持不变。在底部的图表中,您可以在大约 16 秒处看到第一次加载图像的时间点,但您可以看到帧速率没有下降。仅通过目视检查,我看到选择层相交,并且在第一个相交与图像外观之间有一个小的但可观察到的延迟。据我所知,后台加载代码正在按预期工作。

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

于 2013-10-08T14:57:35.073 回答
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];

希望这可以帮助 。

于 2013-10-14T08:02:36.917 回答
4

我的应用程序有一个非常相似的问题,我必须下载大量图像,并且我的 UI 不断更新。以下是解决我的问题的简单教程链接:

NSOperations & NSOperationQueues 教程

于 2013-10-14T14:56:03.857 回答
3

这是好方法:

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

        UIImage * img = [UIImage imageNamed:@"background.jpg"];
        [[self imageView] setImage:img];
    });
}
于 2013-10-04T10:41:00.730 回答
2

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

于 2013-10-04T11:23:03.420 回答
1

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

更换:

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

经过 :

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

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

于 2013-10-15T22:07:16.497 回答
0

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

#import "UIImageView+AFNetworking.h"

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

谢谢

于 2013-10-15T11:11:42.337 回答
0

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

我已经在一个表格视图中使用它并取得了很好的效果,该表格视图具有即使在下载后也加载缓慢的大图像。

于 2013-10-15T06:15:41.170 回答
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];

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

在应用启动时获取捆绑文件引用/路径

于 2013-10-15T06:21:28.770 回答
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;
            });

        });
于 2013-10-15T11:48:47.643 回答
0

iOS 15 中引入了一组方法UIImage来解码图像并在后台线程上异步创建缩略图

func prepareForDisplay(completionHandler: (UIImage?) -> Void)

异步解码图像并提供新图像以在视图和动画中显示。

func prepareThumbnail(of: CGSize, completionHandler: (UIImage?) -> Void)

在后台线程上异步创建指定大小的缩略图。

您还可以使用一组类似的同步API,如果您需要更多地控制您希望解码发生的位置,例如特定队列:
func prepareForDisplay() -> UIImage?
func 准备Thumbnail(of: CGSize) -> UIImage?

于 2022-01-10T15:15:50.820 回答