异步加载图像到UIImage

Van*_*nel 36 cocoa-touch asynchronous uitableview uiimageview ios

我正在使用iOS 5.0 SDK和XCode 4.2开发iOS 4应用程序.

我必须在UITableView中显示一些帖子博客.当我检索所有Web服务数据时,我使用此方法创建UITableViewCell:

- (BlogTableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString* cellIdentifier = @"BlogCell";

    BlogTableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    if (cell == nil)
    {
        NSArray* topLevelObjects =  [[NSBundle mainBundle] loadNibNamed:@"BlogTableViewCell" owner:nil options:nil];

        for(id currentObject in topLevelObjects)
        {
            if ([currentObject isKindOfClass:[BlogTableViewCell class]])
            {
                cell = (BlogTableViewCell *)currentObject;
                break;
            }
        }
    }

    BlogEntry* entry = [blogEntries objectAtIndex:indexPath.row];

    cell.title.text = entry.title;
    cell.text.text = entry.text;
    cell.photo.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:entry.photo]]];

    return cell;
}
Run Code Online (Sandbox Code Playgroud)

但这一行:

cell.photo.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:entry.photo]]];
Run Code Online (Sandbox Code Playgroud)

它太慢了(entry.photo有一个http网址).

有没有办法异步加载该图像?我认为很难,因为tableView:cellForRowAtIndexPath经常被调用.

LJ *_*son 81

我写了一个自定义类来做这个,使用块和GCD:

WebImageOperations.h

#import <Foundation/Foundation.h>

@interface WebImageOperations : NSObject {
}

// This takes in a string and imagedata object and returns imagedata processed on a background thread
+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage;
@end
Run Code Online (Sandbox Code Playgroud)

WebImageOperations.m

#import "WebImageOperations.h"
#import <QuartzCore/QuartzCore.h>

@implementation WebImageOperations


+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage
{
    NSURL *url = [NSURL URLWithString:urlString];

    dispatch_queue_t callerQueue = dispatch_get_current_queue();
    dispatch_queue_t downloadQueue = dispatch_queue_create("com.myapp.processsmagequeue", NULL);
    dispatch_async(downloadQueue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];

        dispatch_async(callerQueue, ^{
            processImage(imageData);
        });
    });
    dispatch_release(downloadQueue);
}

@end
Run Code Online (Sandbox Code Playgroud)

并在您的ViewController中

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    // Pass along the URL to the image (or change it if you are loading there locally)
    [WebImageOperations processImageDataWithURLString:entry.photo andBlock:^(NSData *imageData) {
    if (self.view.window) {
        UIImage *image = [UIImage imageWithData:imageData];

        cell.photo.image = image;
    }

    }];
}
Run Code Online (Sandbox Code Playgroud)

速度非常快,可以在不影响TableView的UI或滚动速度的情况下加载图像.

***注意 - 此示例假定正在使用ARC.如果没有,您将需要在对象上管理自己的版本)

  • 顺便说一句,在你的完成块中,你应该_not_只更新单元格!您无法保证单元格随后不会从屏幕滚动,因此已经出列并重新用于表格的另一行(如果您快速滚动超级或网络连接速度慢,则很重要).你应该调用tableview的`cellForRowAtIndexPath`(不要与表视图控制器的同名方法混淆),并确保它不是'nil`.因此`CustomCell*updateCell = [tableView cellForRowAtIndexPath:indexPath]; if(updateCell)updateCell.photo.image = ...;` (6认同)
  • 这是对提出的问题的一个很好的答案.它至少应该有更多的选票. (4认同)
  • 在iOS 6中不推荐使用dispatch_get_current_queue. (2认同)

bbr*_*ame 52

在iOS 6及更高版本中,dispatch_get_current_queue提供了弃用警告.

这里是作为@ElJay答案上述和由@khanlou制品的合成的替代这里.

在UIImage上创建一个类别:

的UIImage + Helpers.h

@interface UIImage (Helpers)

+ (void) loadFromURL: (NSURL*) url callback:(void (^)(UIImage *image))callback;

@end
Run Code Online (Sandbox Code Playgroud)

的UIImage + Helpers.m

#import "UIImage+Helpers.h"

@implementation UIImage (Helpers)

+ (void) loadFromURL: (NSURL*) url callback:(void (^)(UIImage *image))callback {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];
        dispatch_async(dispatch_get_main_queue(), ^{
            UIImage *image = [UIImage imageWithData:imageData];
            callback(image);
        });
    });
}

@end
Run Code Online (Sandbox Code Playgroud)


tar*_*mes 28

看看SDWebImage:

https://github.com/rs/SDWebImage

这是一套很棒的课程,可以为您处理一切.

蒂姆

  • 如果有人需要调整大小/裁剪功能,我将此库与UIImage + Resize库集成.请在https://github.com/toptierlabs/ImageCacheResize中查看 (3认同)

Ale*_*kov 6

迅速:

extension UIImage {

    // Loads image asynchronously
    class func loadFromURL(url: NSURL, callback: (UIImage)->()) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {

            let imageData = NSData(contentsOfURL: url)
            if let data = imageData {
                dispatch_async(dispatch_get_main_queue(), {
                    if let image = UIImage(data: data) {
                        callback(image)
                    }
                })
            }
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

// First of all remove the old image (required for images in cells)
imageView.image = nil 

// Load image and apply to the view
UIImage.loadFromURL("http://...", callback: { (image: UIImage) -> () in
     self.imageView.image = image
})
Run Code Online (Sandbox Code Playgroud)

  • 考虑处理失败案例 (2认同)

Aks*_*rda 5

斯威夫特 4 | 异步加载图像

创建一个名为 ImageLoader.swift 的新类

import UIKit

class ImageLoader {

var cache = NSCache<AnyObject, AnyObject>()

class var sharedInstance : ImageLoader {
    struct Static {
        static let instance : ImageLoader = ImageLoader()
    }
    return Static.instance
}

func imageForUrl(urlString: String, completionHandler:@escaping (_ image: UIImage?, _ url: String) -> ()) {
        let data: NSData? = self.cache.object(forKey: urlString as AnyObject) as? NSData

        if let imageData = data {
            let image = UIImage(data: imageData as Data)
            DispatchQueue.main.async {
                completionHandler(image, urlString)
            }
            return
        }

    let downloadTask: URLSessionDataTask = URLSession.shared.dataTask(with: URL.init(string: urlString)!) { (data, response, error) in
        if error == nil {
            if data != nil {
                let image = UIImage.init(data: data!)
                self.cache.setObject(data! as AnyObject, forKey: urlString as AnyObject)
                DispatchQueue.main.async {
                    completionHandler(image, urlString)
                }
            }
        } else {
            completionHandler(nil, urlString)
        }
    }
    downloadTask.resume()
    }
}
Run Code Online (Sandbox Code Playgroud)

在 ViewController 类中使用:

ImageLoader.sharedInstance.imageForUrl(urlString: "https://www.logodesignlove.com/images/classic/apple-logo-rob-janoff-01.jpg", completionHandler: { (image, url) in
                if image != nil {
                    self.imageView.image = image
                }
            })
Run Code Online (Sandbox Code Playgroud)