打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
ios UITableView 异步加载图片并防止错位

UITableView 重用 UITableViewCell 并异步加载图片时会出现图片错乱的情况

对错位原因不明白的同学请参考我的另外一篇随笔:http://www.cnblogs.com/lesliefang/p/3619223.html 。

当然大多数情况下可以用 SDWebImage, 这个库功能强大,封装的很好。但自己重头来写可能对问题理解的更深。

SDWebImage 有点复杂,很多人也会参考一下封装出一套适合自己的类库。

基本思路如下:

1 扩展(category) UIImageView, 这样写出的代码更整洁

2 GCD 异步下载 

3 重用 UITableViewCell 加异步下载会出现图片错位,所以每次 cell 渲染时都要预设一个图片 (placeholder),

以覆盖先前由于 cell 重用可能存在的图片, 同时要给 UIImageView 设置 tag 以防止错位。

4 内存 + 文件 二级缓存, 内存缓存基于 NSCache

暂时没有考虑 cell 划出屏幕的情况,一是没看明白 SDWebImage 是怎么判断滑出屏幕并 cancel 掉队列中对应的请求的

二是我觉得用户很多情况下滑下去一般还会滑回来,预加载一下也挺好。坏处是对当前页图片加载性能上有点小影响。

关键代码如下:

1 扩展 UIImageView

@interface UIImageView (AsyncDownload)// 通过为 ImageView 设置 tag 防止错位// tag 指向的永远是当前可见图片的 url, 这样通过 tag 就可以过滤掉已经滑出屏幕的图片的 url@property NSString *tag;- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;@end#import "UIImageView+AsyncDownload.h"@implementation UIImageView (AsyncDownload)- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder{    // 给  ImageView 设置 tag, 指向当前 url    self.tag = [url absoluteString];        // 预设一个图片,可以为 nil    // 主要是为了清除由于复用以前可能存在的图片    self.image = placeholder;        if (url) {        // 异步下载图片        LeslieAsyncImageDownloader *imageLoader = [LeslieAsyncImageDownloader sharedImageLoader];        [imageLoader downloadImageWithURL:url                                 complete:^(UIImage *image, NSError *error, NSURL *imageURL) {                                     // 通过 tag 保证图片被正确的设置                                     if (image && [self.tag isEqualToString:[imageURL absoluteString]]) {                                         self.image = image;                                     }else{                                         NSLog(@"error when download:%@", error);                                     }                                 }];    }}@end

2 GCD 异步下载, 封装了一个 单例 下载类

@implementation LeslieAsyncImageDownloader+(id)sharedImageLoader{    static LeslieAsyncImageDownloader *sharedImageLoader = nil;    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        sharedImageLoader = [[self alloc] init];    });        return sharedImageLoader;}- (void)downloadImageWithURL:(NSURL *)url complete:(ImageDownloadedBlock)completeBlock{    LeslieImageCache *imageCache = [LeslieImageCache sharedCache];    NSString *imageUrl = [url absoluteString];    UIImage *image = [imageCache getImageFromMemoryForkey:imageUrl];    // 先从内存中取    if (image) {        if (completeBlock) {            NSLog(@"image exists in memory");            completeBlock(image,nil,url);        }                return;    }        // 再从文件中取    image = [imageCache getImageFromFileForKey:imageUrl];    if (image) {        if (completeBlock) {            NSLog(@"image exists in file");            completeBlock(image,nil,url);        }                // 重新加入到 NSCache 中        [imageCache cacheImageToMemory:image forKey:imageUrl];                return;    }        // 内存和文件中都没有再从网络下载    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{        NSError * error;        NSData *imgData = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&error];                dispatch_async(dispatch_get_main_queue(), ^{            UIImage *image = [UIImage imageWithData:imgData];                        if (image) {                // 先缓存图片到内存                [imageCache cacheImageToMemory:image forKey:imageUrl];                                // 再缓存图片到文件系统                NSString *extension = [[imageUrl substringFromIndex:imageUrl.length-3] lowercaseString];                NSString *imageType = @"jpg";                                if ([extension isEqualToString:@"jpg"]) {                    imageType = @"jpg";                }else{                    imageType = @"png";                }                                [imageCache cacheImageToFile:image forKey:imageUrl ofType:imageType];            }                        if (completeBlock) {                completeBlock(image,error,url);            }        });    });}@end

3 内存 + 文件 实现二级缓存,封装了一个 单例 缓存类

@implementation LeslieImageCache+(LeslieImageCache*)sharedCache {    static LeslieImageCache *imageCache = nil;    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        imageCache = [[self alloc] init];    });        return imageCache;}-(id)init{    if (self == [super init]) {        ioQueue = dispatch_queue_create("com.leslie.LeslieImageCache", DISPATCH_QUEUE_SERIAL);                memCache = [[NSCache alloc] init];        memCache.name = @"image_cache";                fileManager = [NSFileManager defaultManager];                NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);        cacheDir = [paths objectAtIndex:0];    }        return self;}-(void)cacheImageToMemory:(UIImage*)image forKey:(NSString*)key{    if (image) {        [memCache setObject:image forKey:key];    }}-(UIImage*)getImageFromMemoryForkey:(NSString*)key{    return [memCache objectForKey:key];}-(void)cacheImageToFile:(UIImage*)image forKey:(NSString*)key ofType:(NSString*)imageType{    if (!image || !key ||!imageType) {        return;    }        dispatch_async(ioQueue, ^{        // @"http://lh4.ggpht.com/_loGyjar4MMI/S-InbXaME3I/AAAAAAAADHo/4gNYkbxemFM/s144-c/Frantic.jpg"        // 从 url 中分离出文件名 Frantic.jpg        NSRange range = [key rangeOfString:@"/" options:NSBackwardsSearch];        NSString *filename = [key substringFromIndex:range.location+1];        NSString *filepath = [cacheDir stringByAppendingPathComponent:filename];        NSData *data = nil;                if ([imageType isEqualToString:@"jpg"]) {            data = UIImageJPEGRepresentation(image, 1.0);        }else{            data = UIImagePNGRepresentation(image);        }                if (data) {            [data writeToFile:filepath atomically:YES];        }    });}-(UIImage*)getImageFromFileForKey:(NSString*)key{    if (!key) {        return nil;    }        NSRange range = [key rangeOfString:@"/" options:NSBackwardsSearch];    NSString *filename = [key substringFromIndex:range.location+1];    NSString *filepath = [cacheDir stringByAppendingPathComponent:filename];        if ([fileManager fileExistsAtPath:filepath]) {        UIImage *image = [UIImage imageWithContentsOfFile:filepath];        return image;    }        return nil;}@end

4 使用

自定义 UITableViewCell

@interface LeslieMyTableViewCell : UITableViewCell@property UIImageView *myimage;@end@implementation LeslieMyTableViewCell- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];    if (self) {                self.myimage = [[UIImageView alloc] init];        self.myimage.frame = CGRectMake(10, 10, 60, 60);                [self addSubview:self.myimage];    }        return self;}

cell 被渲染时调用

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{    static NSString *mycellId = @"mycell";        LeslieMyTableViewCell *mycell = [tableView dequeueReusableCellWithIdentifier:mycellId];        if (mycell == nil) {        mycell = [[LeslieMyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:mycellId];    }            NSString *imageUrl = data[indexPath.row];        if (imageUrl!=nil && ![imageUrl isEqualToString:@""]) {        NSURL *url = [NSURL URLWithString:imageUrl];        [mycell.myimage setImageWithURL:url placeholderImage:nil];    }        return mycell;}

demo 地址:https://github.com/lesliebeijing/LeslieAsyncImageLoader.git

 

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
SDWebImage源码解析
ios通过按钮点击异步加载图片
读码笔记-YYWebImage源码 (三) -YYImageCache
iOS 异步下载图片
SDWebImage源码解析之SDWebImageManager的注解
网络获取图片大小自适应
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服