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. CSS 学习-文本 段落

    段落. 首行缩进 text-indent属性 比如缩进  <p style="text-indent: 2em;">这里是内容....</p> 这里是内容这 ...

  2. spring源码研究之IoC容器在web容器中初始化过程

    转载自 http://ljbal.iteye.com/blog/497314 前段时间在公司做了一个项目,项目用了spring框架实现,WEB容器是Tomct 5,虽然说把项目做完了,但是一直对spr ...

  3. 16、Semantic-UI之模态窗口

    16.1 定义模态窗口 示例:定义基础的模态窗口 <!DOCTYPE html> <html lang="en"> <head> <met ...

  4. Android-bindService远程服务启动其他应用的Activity

    Service2应用,在AndroidManifest.xml文件中对外暴露MyService2服务: <!-- 代表在应用程序里,当需要该service时,会自动创建新的进程. android ...

  5. 微软2014实习生招聘笔试第2题 the k-th string

    Time Limit: 10000msCase Time Limit: 1000msMemory Limit: 256MB Description Consider a string set that ...

  6. 用Java实现多线程服务器程序

    一.Java中的服务器程序与多线程 在Java之前,没有一种主流编程语言能够提供对高级网络编程的固有支持.在其他语言环境中,实现网络程序往往需要深入依赖于操作平台的网络API的技术中去,而Java提供 ...

  7. PMBOK项目管理相关知识梳理

    该次梳理,依据PMBOK(第五版),罗列项目管理十三章节重要的知识点. 一.引论1.项目的定义与举例:2.项目.项目组合.项目集与项目组织管理:3.范进本质是风资(范围.进度.成本.质量.风险.资源) ...

  8. Entity Framework中的连接管理

    EF框架对数据库的连接提供了一系列的默认行为,通常情况下不需要我们太多的关注.但是,这种封装,降低了灵活性,有时我们需要对数据库连接加以控制. EF提供了两种方案控制数据库连接: 传递到Context ...

  9. JavaScript作用域详解

         作用域在JavaScript中是非常重要的概念,理解了它对更深入地理解闭包等概念都有很大的帮助,这篇文章就来谈谈我对作用域的理解. 一.全局作用域与局部作用域       在JavaScri ...

  10. loadrunner 11问题汇总

    1.问题描述:安装loadrunner11后,录制脚本时,explore未打开,event为0,录制结果为空.安装环境是window7+ie8+loadrunner11 解决方案: 1.首先要把ie设 ...