NSOperation下載圖片
首先就是根據圖片的url去images中取圖片,如果存在直接就將圖片顯示到cell上,問題是不存在該怎么處理?現在images里面沒有圖片,里面給界面顯示一個占位的圖片,然后根據這張圖片的url也就是圖片的唯一標識符去查看操作隊列中是否存在下載標識符為url的圖片的操作,如果操作隊列里面已經有了下載該張圖片的操作,那么等待操作隊列開啟這個操作的start方法即可,可是如果操作隊列中沒有下載該張圖片的操作,那么就需要先創建一個下載該張圖片的操作,最好給這個操作也添加一個唯一標志符,避免重復添加下載該圖片的操作對象到同一個操作隊列之中。現在已經將下載該圖片的操作對象添加到操作隊列之中了,只等前一個操作對象執行完畢就會取出該操作對象并調用start方法,當然啦,前提是這個操作隊列設置的最大允許并發數是1。當這個操作對象調用start方法執行完下載圖片的任務之后取出下載成功的圖片展示到界面上,剩下的最后一步尤其重要,就是將已經執行完畢的操作對象從操作隊列中移除,可是我就疑惑了,為什么操作隊列不能再操作對象執行完畢之后自動移除這個操作對象呢,簡直就是不科學呀!居然要我們手動去操作隊列里面移除這個已經執行完畢的操作對象,難道只是因為操作隊列無法監聽和管理操作對象里面封裝的任務的執行狀態。這也不科學呀,操作對列這么NB,這點小事不可能做不到呀!當然啦,我們經常用到的圖片下載都是用沙盒來緩存我們所下載的圖片的,知道圖片的url后也首先是看看沙盒里面有沒有,自然,在圖片下載成功的時候,也需要特別注意先要把下載成功的圖片存儲到沙盒里。
// 自己下載圖片
-(void)downloadImage{
// 先從內存緩存中取出圖片
UIImage *image = self.images[app.icon];
if (image) { // 內存中有圖片
cell.imageView.image = image;
} else { // 內存中沒有圖片
// 獲得Library/Caches文件夾
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
// 獲得文件名
NSString *filename = [app.icon lastPathComponent];
// 計算出文件的全路徑
NSString *file = [cachesPath stringByAppendingPathComponent:filename];
// 加載沙盒的文件數據
NSData *data = [NSData dataWithContentsOfFile:file];
if (data) { // 直接利用沙盒中圖片
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
// 存到字典中
self.images[app.icon] = image;
} else { // 下載圖片
cell.imageView.image = [UIImage imageNamed:@"placeholder"];
NSOperation *operation = self.operations[app.icon];
if (operation == nil) { // 這張圖片暫時沒有下載任務
operation = [NSBlockOperation blockOperationWithBlock:^{
// 下載圖片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
// 數據加載失敗
if (data == nil) {
// 移除操作
[self.operations removeObjectForKey:app.icon];
return;
}
UIImage *image = [UIImage imageWithData:data];
// 存到字典中
self.images[app.icon] = image;
// 回到主線程顯示圖片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}];
// 將圖片文件數據寫入沙盒中
[data writeToFile:file atomically:YES];
// 移除操作
[self.operations removeObjectForKey:app.icon];
}];
// 添加到隊列中
[self.queue addOperation:operation];
// 存放到字典中
self.operations[app.icon] = operation;
}
}
}
// 對比一下三方庫
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"placeholder"] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
NSLog(@"下載進度(已經接收的圖片字節數/圖片的總字節數):%f", 1.0 * receivedSize / expectedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"下載完圖片");
}];
NSOperationQueue
添加一段待執行的代碼到NSOperationQueue操作隊列之中,有兩種辦法,其一就是通過一個NSOperation子類的操作對象將待執行的代碼封裝起來,然后通過NSOperationQueue操作隊列的對象調用addOperation這個方法將已經封裝代碼的操作對象添加到隊列之中,這時候NSOperationQueue操作隊列里面的操作對象就會自動調用start方法開始執行封裝的執行代碼,還有就是干脆不實例化操作對象了了,直接通過NSOperationQueue操作隊列調用addOperationWithBlock方法將想要執行的代碼寫到Block之中,這雖然很省力,但是不免要問,這有什么意義呢,我干嘛要在.m文件的方法實現里面實例化一個NSOperationQueue操作隊列然后在執行寫在這個對象的Block參數的代碼塊里面的方法呢,直接把待執行的代碼寫到.m文件多好,根本看不到用NSOperationQueue操作隊列對象的意義呀!哈哈,剛才整理完了demo,發現通過NSOperationQueue操作隊列對象的addOperationWithBlock方法來來添加任務,簡直特別??,因為系統直接就默認這段代碼在子線程中執行了,還有比這更幸福的事情么,我反正想不到開辟一個分線程執行任務更簡單的方法了,暫時真的想不到比這還簡單的方法呀,當然了,我還有一個巨大的收獲,我曾經以為圖片下載有多么多么高大上呢,沒想到本質上就是把那句卡主線的圖片下載代碼當成一個任務放到子線程去執行呀,簡直不要太簡單,好不好?還有一個一意外的收獲就是弄懂了沙盒硬盤緩存和在應用中聲明字典做內存緩存的區別,而且更讓人驚喜的是,無論是硬盤沙盒的讀取操作還是在程序運行時開辟一個字典內存空間存儲圖片都是可以在子線程進行的,簡直不要太炫酷好不好?
操作隊列設置最大并發數
NSOperationQueue的對象調用setMaxConcurrentOperationCount方法,來設置操作隊列最多允許同時執行多少個操作對象,一個操作對象開始執行就表示這個操作對列為這個操作對象開辟了一條新線程,然后這個操作對象就開始調用start方法,將封裝在里面的代碼放在為此操作對象開辟的線程中執行。
操作隊列設置狀態
調用cancelAllOperations方法取消操作隊列里面的所有操作,調用setSuspended方法暫停和恢復操作隊列里面所有的操作對象。也可以針對操作隊列里面的某一個操作對象,然后調用cancel方法取消特定操作對象里面封裝的代碼。
操作隊列管理操作與操作之間的依賴
一說到操作與操作之間的依賴,這個用處就很廣泛了,典型的就是我的直播間的邏輯,要執行很多操作,這些待執行的操作是有嚴格的順序要求的,其實我更多的就是通過Block來實現呀,也可以考慮換一個思路嘛,比如通過操作隊列來設置操作與操作之間的依賴關系。 NSOperation之間可以設置依賴來保證執行順序比如一定要讓操作A執行完后,才能執行操作B,可以這么寫[operationB addDependency:operationA];表示操作B必須在操作A完成結束之后再執行。最激動人心的是,就算A操作對象在隊列1,B操作對象在隊列2,也是可以實現操作A在操作B執行完畢之后再執行。就是這么任性。但是切記切記,不要相互依賴呀,你不要整個A依賴B,又整個B依賴A,你這么做可就讓程序為難了。
SDWebImage工作原理
最占內存資源的還是圖片緩存,問題的關鍵就在我們如何使用SDWebImage庫來緩存圖片。方法就是在調用sd_setImageWithURL之后再加上一個有options選項的方法,SDWebImage默認的其它方法都是默認綜合存儲,也就是內存緩存和磁盤緩存相結合的方式,如果只需要內存緩存,那么在options選項后選擇SDWebImageCacheMemoryOnly就行了。
當調用sd_setImageWithURL方法之后首先將預先放置的本地圖片加載到圖片的位置上,然后SDWebImageManager開始根據URL處理圖片,先從內存的圖片緩存中去查詢是否有圖片,沒有再生成一個從硬盤緩存查找圖片的任務添加到隊列,就算硬盤緩存存在圖片也需要先將圖片添加到內存緩存中,當然可能存在內存緩存的空余內存不夠的情況,必須先清空內存緩存然后在將圖片回調到控制器進行展示。當然如果硬盤緩存里也沒有圖片,就必須通過NSURLConnection來下載圖片,下載完成后自然是異步進行圖片解碼。將解碼的圖片回調到需要的地方進行展示。同時將圖片保存到內存緩存和硬盤緩存,需要注意的是,將圖片寫入硬盤緩存也要異步進行,避免拖慢主線程。更重要的是,SDImageCache在初始化的時候會注冊一些消息通知,在內存警告或退到后臺的時候清理內存圖片緩存,應用結束的時候清理過期圖片