ios UITableView 非同步載入圖片並防止錯位

來源:互聯網
上載者:User

標籤:style   blog   http   color   使用   os   io   檔案   

UITableView 重用 UITableViewCell 並非同步載入圖片時會出現圖片錯亂的情況

對錯位原因不明白的同學請參考我的另外一篇隨筆:http://www.cnblogs.com/lesliefang/p/3619223.html 。

當然大多數情況下可以用 SDWebImage, 這個庫功能強大,封裝的很好。但自己重頭來寫可能對問題理解的更深。

SDWebImage 有點複雜,很多人也會參考一下封裝出一套適合自己的類庫。

基本思路如下:

1 擴充(category) UIImageView, 這樣寫出的代碼更整潔

2 大家都知道圖片要開子線程非同步下載,這裡使用  GCD 

3 重用 UITableViewCell 加非同步下載會出現圖片錯位,所以每次 cell 渲染時都要預設一個圖片 (placeholder),以覆蓋先前由於 cell 重用可能存在的圖片

4 記憶體 + 檔案 二級緩衝, 記憶體緩衝基於 NSCache

暫時沒有考慮 cell 划出螢幕的情況,一是沒看明白 SDWebImage 是怎麼判斷滑出螢幕並 cancel 掉隊列中對應的請求的

二是我覺得使用者很多情況下滑下去一般還會滑回來,預先載入一下也挺好。壞處是對當前頁圖片載入效能上有點小影響。

關鍵代碼如下:

1 擴充 UIImageView

@implementation UIImageView (AsyncDownload)- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder{    // 預設一個圖片,可以為 nil    self.image = placeholder;        if (url) {        // 非同步下載圖片        LeslieAsyncImageDownloader *imageLoader = [LeslieAsyncImageDownloader sharedImageLoader];        [imageLoader downloadImageWithURL:url                                 complete:^(UIImage *image, NSError *error, NSURL *imageURL) {                                     if (image) {                                         // 下載完成後設定圖片                                         self.image = image;                                     }else{                                         NSLog(@"error when download:%@", error);                                     }                                 }];    }}

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

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.