在 iPhone 应用程序中从磁盘加载图像很慢

Posted

技术标签:

【中文标题】在 iPhone 应用程序中从磁盘加载图像很慢【英文标题】:loading images from disk in iPhone app is slow 【发布时间】:2011-11-04 22:04:23 【问题描述】:

在我的 iPhone 应用程序中,我使用 iPhone 的相机拍摄照片并将其保存到磁盘(应用程序的文档文件夹)。这就是我保存它的方式:

[UIImageJPEGRepresentation(photoTaken, 0.0) writeToFile:jpegPath atomically:YES];

使用最大压缩率,我认为从磁盘读取图像会很快。但它不是!我在我的一个视图中使用该图像作为按钮的背景图像。我是这样加载的:

[self.frontButton setBackgroundImage:[UIImage imageWithContentsOfFile:frontPath] forState:UIControlStateNormal];

当我用这个按钮导航到视图时,它很慢而且不稳定。我该如何解决这个问题?

【问题讨论】:

【参考方案1】:

+imageWithContentsOfFile: 是同步的,因此主线程上的 UI 被磁盘操作中的图像加载阻塞并导致断断续续。解决方案是使用从磁盘异步加载文件的方法。您也可以在后台线程中执行此操作。这可以通过将+imageWithContentsOfFile: 包装在dispatch_async() 中,然后在包装-setBackgroundImage: 的主队列上嵌套dispatch_async() 来轻松完成,因为UIKit 方法需要在主线程上运行。如果您希望图像在视图加载后立即显示,则需要从磁盘预缓存图像,以便在视图出现时立即将其存储在内存中。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^

    UIImage *image = [UIImage imageWithContentsOfFile:frontPath];

    dispatch_async(dispatch_get_main_queue(), ^
        [self.frontButton setBackgroundImage:image forState:UIControlStateNormal];
    );

);

顺便说一句,如果按钮图像发生渐变,请考虑使用以下属性来确保从磁盘加载的图像文件很小:

- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets

或(已弃用,仅在需要支持 ios 4.x 时使用):

- (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight

【讨论】:

@Andrew 你如何预缓存? re:"你需要预先缓存磁盘中的图像" @Van Du Tran,在这种情况下,预缓存意味着在需要之前(例如,在视图出现之前)将图像加载到 UIImage 中。这样,当您加载视图时,图像已经保存在内存中,因此无需磁盘 I/O 即可将其显示在屏幕上。 看起来 Apple is warning 反对在后台线程上使用 UIKit 类。 UIImage is thread-safe。苹果声明:注意Because image objects are immutable, you cannot change their properties after creation. Most image properties are set automatically using metadata in the accompanying image file or image data. The immutable nature of image objects also means that they are safe to create and use from any thread.【参考方案2】:

这是我知道的更快的方法。您需要导入#import <ImageIO/ImageIO.h>

我在滚动期间使用此代码在滚动视图内下载和压缩图像,您几乎不会注意到延迟。

CGImageSourceRef src = CGImageSourceCreateWithData((CFDataRef)mutableData, NULL);
CFDictionaryRef options = (CFDictionaryRef)[[NSDictionary alloc] initWithObjectsAndKeys:(id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailWithTransform, (id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailFromImageIfAbsent, (id)[NSNumber numberWithDouble:200.0], (id)kCGImageSourceThumbnailMaxPixelSize, nil];
CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src, 0, options);

UIImage *image = [[UIImage alloc] initWithCGImage:thumbnail];
// Cache
NSString *fileName = @"fileName.jpg";
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"thumbnail"];
path = [path stringByAppendingPathComponent:fileName];
if ([UIImagePNGRepresentation(image) writeToFile:path atomically:YES]) 
    // Success

【讨论】:

如果这段代码对你来说仍然很慢,你只需要把它放在一个线程中。请注意,“200.0”是您想要的最大尺寸(以像素为单位)。【参考方案3】:

我遇到了一个非常相似的问题,我必须从目录中加载数百张图像。如果我使用 UIImage(contentsOfFile:) 方法,我的表现会很慢。以下方法将我的性能提高到 70 %。

类 ImageThumbnailGenerator: ThumbnailGenerator 私人让网址:网址

    init(url: URL) 
       self.url = url
    

func generate(size: CGSize) -> UIImage? 
    guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil) else 
        return nil
    

    let options: [NSString: Any] = [
        kCGImageSourceThumbnailMaxPixelSize: Double(max(size.width, size.height) * UIScreen.main.scale),
        kCGImageSourceCreateThumbnailFromImageIfAbsent: true
    ]

    return CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as NSDictionary).flatMap  UIImage(cgImage: $0) 
 

【讨论】:

以上是关于在 iPhone 应用程序中从磁盘加载图像很慢的主要内容,如果未能解决你的问题,请参考以下文章

在 ios 应用程序中从磁盘加载大图像

保存或加载图像时 iPhone 设备的响应时间很慢

在 Unity3d 中从 iphone 加载图像

在 tableView 中加载 iPhone 磁盘图像(像照片应用程序)

iPhone:如何在应用程序目录中将图像写入磁盘

在 Flutter/Dart 中从字节加载图像的问题