iOS-網狀圖片通過NSOperation線程依賴非同步載入並儲存到沙箱中
在iOS開發中經常會遇到下載好多較大圖片並且在二級介面展示到UIImageView的情況,例如探探中多卡片的圖片展示。
當然如果將圖片下載這種極耗時的操作放在主線程操作會造成程式假死的狀況,所以考慮使用在多線程非同步載入並且添加線程間依賴的方式,儘可能好的提高使用者體驗。
在這之前需要一些知識儲備:1.線程依賴。2.沙箱儲存
1.線程依賴.
目前在 iOS 和 OS X 中有兩套先進的同步 API 可供我們使用:NSOperation 和 GCD 。其中 GCD 是基於 C 的底層的 API ,而 NSOperation 則是 GCD 實現的 Objective-C API。 雖然 NSOperation 是基於 GCD 實現的,我們可以用NSOperation 輕易的實現一些 GCD 要寫大量代碼的事情。
操作隊列(operation queue)是由 GCD 提供的一個隊列模型的 Cocoa 抽象。GCD 提供了更加底層的控制,而操作隊列則在 GCD 之上實現了一些方便的功能,這些功能對於 app 的開發人員來說通常是最好最安全的選擇。
//NSOperationQueue 線程之前添加依賴操作-(void)dependency{ /** 假設有A、B、C三個操作,要求: 1. 3個操作都非同步執行 2. 操作C依賴於操作B 3. 操作B依賴於操作A */ //建立一個隊列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //可開闢線程的最大數量 queue.maxConcurrentOperationCount = 3; //建立三個任務 NSBlockOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"A任務當前線程為:%@", [NSThread currentThread]); }]; NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"B任務當前線程為:%@", [NSThread currentThread]); }]; NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"C任務當前線程為:%@", [NSThread currentThread]); }]; //設定三個任務相互依賴 // operationB 任務依賴於 operationA [operationB addDependency:operationA]; // operationC 任務依賴於 operationB [operationC addDependency:operationB]; //添加操作到隊列中(自動非同步執行任務,並發) [queue addOperation:operationA]; [queue addOperation:operationB]; [queue addOperation:operationC];}
2.沙箱儲存
iOS 資料存放區方式不再過多累述
直接上代碼:
//1.擷取本地cache檔案路徑 NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; //2.根據檔案URL最後檔案名稱作為檔案名稱儲存到本地 -> 檔案名稱 NSString *imageFilePath = [cachePath stringByAppendingPathComponent:imageName]; //3.寫入檔案 [UIImagePNGRepresentation(image) writeToFile:imageFilePath atomically:YES];
儲備完成,言歸正傳》》
無論做什麼先明確目標:
網路請求成功得到眾多圖片路徑,需要下載並且展示在二級介面中?
實現思路:
1.NSOperationQueue 多線程(添加依賴)將下載圖片操作放在不同線程中執行。
2.如何添加依賴:跳轉操作需要依賴於所有下載任務執行完畢後進行
3.將下載的圖片資料儲存在沙箱中(緩衝圖片一般放在Cache檔案夾中,可以以後清除緩衝)- 此處需要保證沙箱中不存在此資料
4.跳轉,展示多圖介面
實現代碼:
首先添加NSOperationQueue屬性,並懶載入
/** 建立一個隊列 */@property (nonatomic, strong) NSOperationQueue *queue;@end@implementation RadarController//下載隊列-(NSOperationQueue *)queue{ if (_queue == nil) { _queue = [[NSOperationQueue alloc] init]; //可開闢線程最大值 [_queue setMaxConcurrentOperationCount:10]; } return _queue;}
網路請求得到圖片路徑,使用效率最高apple原生NSJSONSerialization的解析JSON資料
//JSON 解析 蘋果原生效率最高 NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil]; NSLog(@"請求成功... %@",result); NSDictionary *dataDict = [result objectForKey:@"result"];
解析成功資料:
最終的跳轉操作(介面跳轉)<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">#pragma mark - 跳轉到二級介面展示所下載的圖片- (void)startUpdatingRadar { typeof(self) __weak weakSelf = self; //延時操作,在0.1秒後在主線程進行push操作 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ DiffusionController *diffusion = [[DiffusionController alloc] initWithJokeImageArr:_jokeImageDataArrM]; [diffusion setHidesBottomBarWhenPushed:YES]; [self.navigationController pushViewController:diffusion animated:YES]; });}
圖片下載和沙箱儲存方法:傳入圖片路徑和圖片名(沙箱中儲存的檔案名稱)的下載進而儲存沙箱的操作
#pragma mark - 根據請求到的圖片路徑網路下載圖片//需要將此下載操作放在非同步線程中進行!//根據圖片路徑URL -> 添加下載任務(非同步線程)-> 將下載的圖片儲存到本地 -> 以便在二級介面直接從檔案中讀取-(void)downloadImageWithURL:(NSString *)URLStr WithImageName:(NSString *)imageName{ //使用線程依賴 @autoreleasepool { NSLog(@"當期啊線程編號:%@",[NSThread currentThread]); //子線程裡面的runloop預設不開啟,也就意味著不會自動建立自動釋放池,子線程裡面autorelease的對象 就會沒有池子釋放。也就一位置偶棉沒有辦法進行釋放造成記憶體泄露,所以需要手動建立 //1.擷取本地cache檔案路徑 NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; //2.根據檔案URL最後檔案名稱作為檔案名稱儲存到本地 -> 檔案名稱 NSString *imageFilePath = [cachePath stringByAppendingPathComponent:imageName]; //如果當前檔案名稱在cache檔案夾中不存在,寫入檔案 if (![self isFileExist:imageName]) { //下載圖片 NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:URLStr]]; //初始化圖片 UIImage *image = [UIImage imageWithData:imageData]; //將下載的圖片儲存到本地 //3.寫入檔案// [UIImagePNGRepresentation(image) writeToFile:imageFilePath atomically:YES]; //或者->(此方法會減少緩衝大小,但是圖片會不清晰) [UIImageJPEGRepresentation(image, 0.8) writeToFile:imageFilePath atomically:YES]; } }}//判斷檔案是否已經在沙箱中已經存在?-(BOOL) isFileExist:(NSString *)fileName{ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *path = [paths objectAtIndex:0]; NSString *filePath = [path stringByAppendingPathComponent:fileName]; NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL result = [fileManager fileExistsAtPath:filePath]; NSLog(@"這個檔案已經存在?:%@",result?@"存在":@"不存在"); return result;}
核心代碼,所有下載任務執行完畢後執行主任務->
//建立主任務-跳轉頁面 __block NSBlockOperation *jumpMainOperaion = [NSBlockOperation blockOperationWithBlock:^{ [self startUpdatingRadar]; }]; //模型數組 _jokeImageDataArrM = [[NSMutableArray alloc] init]; //遍曆所有圖片路徑,並添加到下載隊列中 for (NSDictionary *imageDict in (NSArray *)[dataDict objectForKey:@"data"]) { JokeImageData *jokeImageData = [[JokeImageData alloc] init]; [jokeImageData setContent:[imageDict objectForKey:@"content"]]; NSString *imageUrl = [imageDict objectForKey:@"url"]; [jokeImageData setUrl:imageUrl]; NSString *imageName = [imageDict objectForKey:@"hashId"]; [jokeImageData setHashId:imageName]; [jokeImageData setUpdatetime:[imageDict objectForKey:@"updatetime"]] ; //非同步線程下載 NSBlockOperation *currentBlockOperation = [NSBlockOperation blockOperationWithBlock:^{ //圖片下載操作 [self downloadImageWithURL:imageUrl WithImageName:imageName]; }]; //添加依賴 [jumpMainOperaion addDependency:currentBlockOperation]; //添加到線程隊列 [self.queue addOperation:currentBlockOperation]; //模型數組 [_jokeImageDataArrM addObject:jokeImageData]; } //將最後的操作最後添加進隊列 [self.queue addOperation:jumpMainOperaion];
最後附一簡要流程圖: