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. numpy.convolve()

    卷积函数: numpy.convolve(a, v, mode='full') Parameters: a : (N,) array_like First one-dimensional input ...

  2. sqlite数据库的char,varchar,text,nchar,nvarchar,ntext的区别(转)

    sqlite数据库存储table1.CHAR.CHAR存储定长数据很方便,CHAR字段上的索引效率级高,比如定义char(10),那么不论你存储的数据是否达到了10个字节,都要占去10个字节的空间,不 ...

  3. Linq的使用场景简介和认识

    一:C#的一个分支Linq 二:学Linq需要有一些基础知识 1. var 隐式类型 2. 匿名类型/匿名方法 3. 自动属性 4. 委托/泛型的委托 5. lambda 6. 扩展方法 7. 对象初 ...

  4. 【C#】泛型

    泛型是一个非常有用的技术,在博客园里面有太多说到泛型的好文章,这里我推荐一篇我个人觉得非常全面,也非常齐全的文章. (重造轮子很傻!!!) C# -- 泛型(1) C# -- 泛型(2) C# -- ...

  5. vs附加调试

    已解决.项目-属性-调试-启用调试器选择启用本机代码调试,不知道怎么的选成启用Visual Studio承载进程了.

  6. EF修改model自动更新数据库

    最近用MVC+EF学习时遇到修改model后而数据库没更新报错,就在网上找关于数据迁移自动更新数据库的,折腾了大半天终于弄了出来 第一步:在程序包管理器控制台里: Enable-Migrations ...

  7. django 快捷代码提示

    1.右键项目,Mark Directory As Source Root 2.settings配置 3.import包时可忽略app名了

  8. EntityFramework Code First 模式下使用数据迁移

    启用数据迁移 在程序包管理控制台选择安装了EntityFramework的项目,键入如下指令以启EF用数迁移. Enable-Migrations 命令成功运行后,所选项目下会添加名为Migratio ...

  9. PXE-cobbler 无人值守装机------续

    上篇学习了cobbler pxe 安装的大致流程以及采用默认分区方式安装centos7系统. 这里深入学习ks文件的具体参数 . ks.cfg文件又叫kickstart.cfg文件,主要被用于linu ...

  10. OI网络流 简单学习笔记

    持续更新! 基本上只是整理了一下框架,具体的学习给出了个人认为比较好的博客的链接. ..怎么说呢,最基础的模板我就我不说了吧qwq,具体可以参考一下这位大佬写的博客:最大流,最小割,费用流 费用流 跑 ...