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, ), ^{
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 异步加载图片并防止错位的更多相关文章
- IOS中UITableView异步加载图片的实现
本文转载至 http://blog.csdn.net/enuola/article/details/8639404 最近做一个项目,需要用到UITableView异步加载图片的例子,看到网上有一个E ...
- listview异步加载图片并防止错位
android listview 异步加载图片并防止错位 网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 conver ...
- android listview 异步加载图片并防止错位
网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 convertView 不会出现错位现象, 重用 convertVie ...
- Android的ListView异步加载图片时,错位、重复、闪烁问题的分析及解决方法
Android ListView异步加载图片错位.重复.闪烁分析以及解决方案,具体问题分析以及解决方案请看下文. 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图 ...
- iOS NSOperation 异步加载图片 封装NSOperation 代理更新
#import <Foundation/Foundation.h> @class MYOperation; @protocol MYOperationDelecate <NSObje ...
- listview 异步加载图片并防止错位
1.图片错位原理: 如果我们只是简单显示list中数据,而没用convertview的复用机制和异步操作,就不会产生图片错位:重用convertview但没用异步,也不会有错位现象.但我们的项目中li ...
- IOS学习之路二十三(EGOImageLoading异步加载图片开源框架使用)
EGOImageLoading 是一个用的比较多的异步加载图片的第三方类库,简化开发过程,我们直接传入图片的url,这个类库就会自动帮我们异步加载和缓存工作:当从网上获取图片时,如果网速慢图片短时间内 ...
- 多线程异步加载图片async_pictures
异步加载图片 目标:在表格中异步加载网络图片 目的: 模拟 SDWebImage 基本功能实现 理解 SDWebImage 的底层实现机制 SDWebImage 是非常著名的网络图片处理框架,目前国内 ...
- 实例演示Android异步加载图片
本文给大家演示异步加载图片的分析过程.让大家了解异步加载图片的好处,以及如何更新UI.首先给出main.xml布局文件:简单来说就是 LinearLayout 布局,其下放了2个TextView和5个 ...
随机推荐
- Javascript 的addEventListener()及attachEvent()对比
Mozilla中: addEventListener的使用方式: target.addEventListener(type, listener, useCapture); target: 文档节点.d ...
- 每天多学一个Linux命令--man
今天学习的是“有问题,找男人”.man是manual(操作说明)的简写.它提供强大的帮助文档,当你需要查看某个命令的参数时不必到处上网查找,只要man一下即可. 第一列代表的是章节代号,第二列代表的是 ...
- select2的一些隐藏功能
select 3.5版本的说明文档里面存在 http://select2.github.io/select2/index.html option选项 sortResults query为查询字符串
- Android自定义组件之自动换行及宽度自适应View:WordWrapView
目的: 自定义一个ViewGroup,里面的子view都是TextView,每个子view TextView的宽度随内容自适应且每行的子View的个数自适应,并可以自动换行 一:效果图 二:代码 整 ...
- Thrift框架学习
参考文章:1.http://www.kankanews.com/ICkengine/archives/54084.shtml 2.http://www.cnblogs.com/liping135991 ...
- mysql 循环插入
在mysql添加测试数据,想和mssql一样用循环实现,发现不管怎么样都执行失败 经查询发现mysql不支持匿名块,只能先创建出一个存储过程,执行,然后删除 CREATE PROCEDURE test ...
- Spring Boot - 记录日志
比自己写文本日志的好处 默认定义好了一些日志级别,会记录当前使用的级别以上的日志,通常线上环境设置的级别较高记得较少 有一些自动split之类的功能 Commons-logging 日志级别:TRAC ...
- SQL SERVER先判断视图是否存在然后再创建视图的语句
如果我们的语句为: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 IF NOT EXISTS(SELECT 1 FROM sys.views WHERE name='Report_I ...
- 【Apache系列】linux下Apache的常用操作
1. 启动/停止 1.1假设你的apahce安装目录为/usr/local/apache2,这些方法适合任何情况 apahce启动命令: /usr/local/apache2/bin/apachect ...
- [网络流24题] 最长K可重区间集问题
题目链接:戳我 当时刷24题的时候偷了懒,没有写完,结果落下这道题没有写qwq结果今天考试T3中就有一部分要用到这个思想,蒟蒻我硬是没有想到网络流呜呜呜 最大费用流. 就是我们考虑将问题转化一下,转化 ...