寫在前面
在iOS開發中,無論是在UITableView還是在UICollectionView中,通過網絡獲取圖片設置到cell上是較為常見的需求。盡管有很多現存的第三方庫可以將下載和緩存功能都封裝好了供開發者使用,但從學習的角度出發,看懂源碼,理解其中的原理,結合自身的實際需求寫出自己的代碼是很必要的。在剛結束的Demo中,有用到異步圖片下載功能,這篇筆記就是對整個實現的簡單整理。
基本思路
•cell中添加一個UIImageView
•cell擁有url,發起下載請求,注冊下次完成通告,在通告處理時間中獲取下載圖片並設置
•下載管理類負責開啟下載線程和各種緩存(內存+文件),下載完成後發送下載完成通告
•為避免cell重用和異步下載造成的圖片錯位,cell在發起下載前為自身imageView設置默認圖片,同時為imageView設置tag
整體框架
關鍵代碼
cell初始化,並注冊下載完成通告
@interface SQPhotoCell () @property (strong, nonatomic) UIImageView *photoView; //Tag指向當前可見圖片的url,可過濾掉已經滑出屏幕的圖片的url @property (strong, nonatomic) NSString *imageViewTag; @end -(id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { _photoView = [[UIImageView alloc] initWithFrame:CGRectZero]; _photoView.userInteractionEnabled = YES; [self.contentView addSubview:_photoView]; _imageViewTag = @""; //注冊下載完成通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadCallback:) name:NOTIFICATION_DOWNLOAD_CALLBACK object:nil]; } return self; }
cell通知處理事件
//通知處理事件 - (void)downloadCallback:(NSNotification *)noti { NSDictionary *notiDic = noti.userInfo; NSString *urlStr = [notiDic objectForKey:@"urlStr"]; UIImage *image = [notiDic objectForKey:@"image"]; if ([self.imageViewTag isEqualToString:urlStr]) { self.photoView.image = image; } }
cell發起下載請求
- (void)setImageWithURL:(NSString *)urlStr placeholder:(UIImage *)placeholder { self.imageViewTag = urlStr; //預設圖片,用於清除復用以前可能存在的圖片 self.photoView.image = placeholder; if (urlStr) { SQWebImageManager *manager = [SQWebImageManager sharedManager]; [manager downloadImageWithURLString:urlStr]; } [self setNeedsDisplay]; }
下載管理類下載函數
- (void)downloadImageWithURLString:(NSString *)urlStr { // 1.判斷內存緩存 {urlStr: image} UIImage *cacheImage = [self.imageCache objectForKey:urlStr]; if (cacheImage != nil) { //發出下載完成的通知,並傳回urlStr和圖片 [self postDownloadCompleteNotification:urlStr withImage:cacheImage]; return; } // 2.判斷沙盒緩存 NSString *cacheImagePath = [self cacheImagePathWithURLString:urlStr]; cacheImage = [UIImage imageWithContentsOfFile:cacheImagePath]; if (cacheImage != nil) { // 從沙盒中讀取到了圖片,設置到內存緩存中,方便下次可以直接從內存中讀取 [self.imageCache setObject:cacheImage forKey:urlStr]; // 返回圖片 [self postDownloadCompleteNotification:urlStr withImage:cacheImage]; return; } // 3.判斷操作緩存,防止圖片多次下載 {urlStr: operation} if (self.operationCache[urlStr] != nil) { // 有操作正在下載這張圖片 NSLog(@"有操作正在下載這張圖片"); return; } // 1.定義下載圖片操作 SQDownloadOperation *downloadOperation = [SQDownloadOperation downloadOperationWithURLString:urlStr cacheImagePath:cacheImagePath]; // 設置操作下載完成的回調,當 downloadOperation 的 main 方法執行完成的時候回調用 __weak typeof(downloadOperation) weakDownloadOperation = downloadOperation; downloadOperation.completionBlock = ^() { // 1. 獲取下載完成的圖像 UIImage *image = [weakDownloadOperation getDownloadImage]; // 2. 從操作緩沖池中刪除操作 [self.operationCache removeObjectForKey:urlStr]; // 3. 判斷圖像是否為空(縮略圖) if (image != nil) { // 設置下載的圖片到圖片內存緩存中 [self.imageCache setObject:image forKey:urlStr]; // 4. 主線程回調 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ //發出下載完成通告 [self postDownloadCompleteNotification:urlStr withImage:image]; }]; } else { //如果圖片為空,返回下載失敗時的默認圖片 image = [UIImage imageNamed:@"default.jpg"]; // 4. 主線程回調 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ //發出下載完成通告 [self postDownloadCompleteNotification:urlStr withImage:image]; }]; } }; // 2.將下載圖片操作添加到隊列中 [self.downloadQueue addOperation:downloadOperation]; // 3.將下載圖片操作添加到下載操作緩存中 [self.operationCache setObject:downloadOperation forKey:urlStr]; } - (void)postDownloadCompleteNotification:(NSString *)urlStr withImage:(UIImage *)image { NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:urlStr, @"urlStr", image, @"image",nil]; [[NSNotificationCenter defaultCenter]postNotificationName:NOTIFICATION_DOWNLOAD_CALLBACK object:nil userInfo:dic]; }
控制器中使用
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { SQPhotoCell *cell = (SQPhotoCell *)[collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath]; UIImage *placeholder = [UIImage imageNamed:@"gray.jpg"]; NSString *imageUrl = @"http://www.taopic.com/uploads/allimg/110925/9117-11092509545328.jpg"; [cell setImageWithURL:imageUrl placeholder:placeholder]; return cell; }
寫在後面
這個異步下載圖片的思路是仿照SDWebImage的,雖然可以直接看到源碼,也有一些文章和博客講解思路,但自己在沒有接觸過多線程編程的情況下學習這個下載思路還是花了挺多時間的。前期一直都有些著急,想要趕緊做出來,在對好多東西都是懵懵懂懂的情況下就去做了,後來才慢慢意識到,其實慢就是快,慢下來,把問題想清楚了再去實施雖然前期感覺是不太好的,但到越到後面就越能發現這種慢的好處。