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, ), ^{
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-] 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:];
} 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+];
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+];
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(, , , ); [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

ios UITableView 异步加载图片并防止错位的更多相关文章

  1. IOS中UITableView异步加载图片的实现

    本文转载至 http://blog.csdn.net/enuola/article/details/8639404  最近做一个项目,需要用到UITableView异步加载图片的例子,看到网上有一个E ...

  2. listview异步加载图片并防止错位

    android listview 异步加载图片并防止错位 网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 conver ...

  3. android listview 异步加载图片并防止错位

    网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 convertView 不会出现错位现象, 重用 convertVie ...

  4. Android的ListView异步加载图片时,错位、重复、闪烁问题的分析及解决方法

    Android ListView异步加载图片错位.重复.闪烁分析以及解决方案,具体问题分析以及解决方案请看下文. 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图 ...

  5. iOS NSOperation 异步加载图片 封装NSOperation 代理更新

    #import <Foundation/Foundation.h> @class MYOperation; @protocol MYOperationDelecate <NSObje ...

  6. listview 异步加载图片并防止错位

    1.图片错位原理: 如果我们只是简单显示list中数据,而没用convertview的复用机制和异步操作,就不会产生图片错位:重用convertview但没用异步,也不会有错位现象.但我们的项目中li ...

  7. IOS学习之路二十三(EGOImageLoading异步加载图片开源框架使用)

    EGOImageLoading 是一个用的比较多的异步加载图片的第三方类库,简化开发过程,我们直接传入图片的url,这个类库就会自动帮我们异步加载和缓存工作:当从网上获取图片时,如果网速慢图片短时间内 ...

  8. 多线程异步加载图片async_pictures

    异步加载图片 目标:在表格中异步加载网络图片 目的: 模拟 SDWebImage 基本功能实现 理解 SDWebImage 的底层实现机制 SDWebImage 是非常著名的网络图片处理框架,目前国内 ...

  9. 实例演示Android异步加载图片

    本文给大家演示异步加载图片的分析过程.让大家了解异步加载图片的好处,以及如何更新UI.首先给出main.xml布局文件:简单来说就是 LinearLayout 布局,其下放了2个TextView和5个 ...

随机推荐

  1. Can't place multiple pins assigned to pin location Pin_F16

    Can't place multiple pins assigned to pin location Pin_F16 在我们芯航线FPGA开发板上,使用了一片128Mbit的SDRAM存储器.当大家在 ...

  2. Autoconf 中文手册

    Autoconf Autoconf Creating Automatic Configuration Scripts Edition 2.13, for Autoconf version 2.13 D ...

  3. XE5安卓手机要求

    1 ARMv7 的 CPU v6 的肯定不支持.2 黑屏是因为你的手机 CPU 不支持 NEON 特性.或者是 T2 CPU.3 系统版本  2.3.3 到 2.3.9 或者 4.0 以上.4. SD ...

  4. android app启动就闪退怎么办?

    开发过程中,如遇到android app启动就闪退,不要急,直接进入调试模式运行app,就会取得出错的原因. http://blog.sina.com.cn/s/blog_44fa172f0102wg ...

  5. Oracle SQl优化总结

    对数据库技术的热爱是我唯一的安慰,毕竟这是自己喜欢的事情,还可以做下去. 因为客户项目的需要,我又开始接触Oracle,大部分工作在工作流的优化和业务数据的排查上.为了更好的做这份工作,我有参考过or ...

  6. Delegate   Func  Action  Predicate default() 知识点

    看仓储模式,有代码写到这几个关键字,陌生,记录下来.       定义一个类型,此类型抽象化了相似结构的某一类方法,因此我们能将此类型代表的方法作为参数进行传递.      Delegate至少0个参 ...

  7. 【07】循序渐进学 docker:数据持久化

    写在前面的话 学到这里相信有心的朋友都发现问题了,我们每次都会去删掉容器,在创建新的容器.那数据怎么办?岂不删库跑路了? 就算不是数据库,假设公司有日志保留的需求,那每一次发布岂不日志都被干掉了? D ...

  8. 在线绘图网站 UML、思维导图、 流程图、 用例图等等

    https://www.processon.com/ 用我的微信登录即可 帐号是 QQ邮箱

  9. WEB新手之serialize’s revenge

    最后一道题. 这道题提示比较少,一点也不友好.F12也没有什么线索.无奈之下用御剑扫下后台,发现了一个叫robots的txt文件. 打开robots.txt文件,可以得到一段代码,如下图所示. 审查代 ...

  10. Run Faster-JAVA

    又好久没有写点啥了,平时都忙于工作,忙于应付工作中的问题,各种吸收却并没有好好的消化,该是"反刍"一下的时候了. 本篇名叫"Run Faster,JAVA",其 ...