最近在做一個iOS手機項目的時候,遇到一個奇怪的問題,這里跟大家分享一下。
一、問題重現
1、啟動App后,通過http請求下載了一個1.jpg文件到Cache目錄下,下載成功之后,將圖片顯示在界面上;(圖1)
2、此時殺掉進程,再次啟動App后,圖片可以正常顯示,然后點擊一個按鈕刪除剛剛下載的圖片;(圖2)
3、此時,將App壓后台,再喚起,原來顯示的圖片消失了!!!(圖3)
圖1: 圖2: 圖3:
這里我們先貼一下代碼,用代碼來說明問題:
1 @implementation ViewController 2 3 - (void)viewDidLoad { 4 [super viewDidLoad]; 5 6 _imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; 7 _imageView.backgroundColor = [UIColor blackColor]; 8 _imageView.contentMode = UIViewContentModeScaleToFill; 9 [self.view addSubview:_imageView]; 10 11 _clearButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 12 _clearButton.frame = CGRectMake(0, 0, 100, 30); 13 _clearButton.center = self.view.center; 14 [_clearButton setTitle:@"清理緩存" forState:UIControlStateNormal]; 15 [_clearButton addTarget:self action:@selector(clearCache) forControlEvents:UIControlEventTouchUpInside]; 16 [self.view addSubview:_clearButton]; 17 } 18 19 - (void)viewDidAppear:(BOOL)animated 20 { 21 [super viewDidAppear:animated]; 22 23 [self showImage]; 24 } 25 26 - (NSString *)imagePath 27 { 28 NSArray *pathcaches=NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 29 NSString *cacheDirectory = [pathcaches objectAtIndex:0]; 30 return [cacheDirectory stringByAppendingString:@"1.jpg"]; 31 } 32 33 // 顯示圖片 34 - (void)showImage 35 { 36 NSString *imageUrl = @"http://pic2.desk.chinaz.com/file/201203/6/chuangyisjbz1_p.jpg"; 37 NSURL *imageURL = [NSURL URLWithString:imageUrl]; 38 NSString *imagePath = [self imagePath]; 39 40 UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; 41 if (image) { 42 // 第二次啟動App,緩存文件存在時,通過[UIImage imageWithContentsOfFile:]初始化 43 _imageView.image = image; 44 } else { 45 // 第一次啟動APP,下載圖片成功后,通過[UIImage imageWithData:]初始化 46 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 47 NSData *data = [NSData dataWithContentsOfURL:imageURL]; 48 if (data) { 49 dispatch_async(dispatch_get_main_queue(), ^{ 50 [data writeToFile:imagePath atomically:YES]; 51 _imageView.image = [UIImage imageWithData:data]; 52 }); 53 } 54 }); 55 } 56 } 57 58 // 清理緩存 59 - (void)clearCache 60 { 61 [[NSFileManager defaultManager] removeItemAtPath:[self imagePath] error:nil]; 62 } 63 64 @end
二、原因分析
產生這個原因的核心原因在於UIImage的初始化方法。
1、直接使用文件初始化圖片
UIImage *image = [UIImage imageWithContentsOfFile:imagePath]
當殺掉進程,第二次啟動App的時候,緩存文件存在,則采用上面的方式初始化圖片;清除緩存,壓后台,再喚起,注意觀察Console區域,會發現如下提示信息:
Mar 9 20:52:02 Demo[38525] <Error>: ImageIO: CGImageReadCreateDataWithMappedFile 'open' failed '/Users/yanzhi/Library/Developer/CoreSimulator/Devices/30EE295B-C260-4A5E-9446-362D05D50C0B/data/Containers/Data/Application/94D96217-D6DB-4BFC-BFD7-60FB66EA7A9E/Library/Caches1.jpg' error = 2 (No such file or directory)
這里,需要注意,壓后台,再喚起,我們並沒有再次執行imageView.image = image的操作,但是為什么圖片就沒有了呢?同時比較奇怪的是,為什么會輸出一個ImageIO錯誤。
注意一下關於[UIImage initWithContentsOfFile:]方法的官方文檔說明:
- (instancetype)initWithContentsOfFile:(NSString *)path Discussion This method loads the image data into memory and marks it as purgeable. If the data is purged and needs to be reloaded, the image object loads that data again from the specified path. 這個方法加載圖片數據到內存中並將其標記為“可清除”。如果內存中圖片被清除,需要重新加載時,這個Image對象需要再次從指定的path中加載圖像數據。
解釋一下,[UIImage imageWithContentsOfFile:]沒有上面的說明信息,僅僅在[UIImage initWithContentsOfFile:]方法中有這段說明。
正如上面文檔說明,當我們壓后台的時候,內存中的Image對象是可以清除,於是就被系統回收掉該內存空間;再次喚起的時候,這個Image對象會嘗試重新加載該Path所指向的文件;但是該文件已經被刪除掉,因此系統在重新加載圖片的時候,就出現了ImageIO的錯誤數據,於是界面也無法再次展示該圖片。
我們可以理解為:使用initWithContentsOfFile的時候,系統為我們的Image對象和Path指向的文件做了一個映射Map,當Image對象被清理掉后,需要再次使用該Image對象時,會自動從Path指向的文件中去讀取數據。
因此,當大家使用initWithContentsOfFile或imageWithContentsOfFile去初始化圖片的時候,切記注意你的圖片文件是否可能被清理掉!
2、使用NSData轉換初始化圖片
NSData *data = [NSData dataWithContentsOfFile:path]; UIImage *image = [UIImage imageWithData:data];
當使用NSData作為一個中間對象來轉換的時候,如果path文件被刪除了,但是對應的data對象並不會被清理掉,始終會在內存中,那么由此生成Image對象也不會被清理。
大家可以做一個實驗,使用這兩個方法替換上方代碼片段中的[UIImage imageWithContentsOfFile:]方法,重新驗證一下,你會發現同樣的場景,圖片始終會正常顯示。
三、總結
1、initWithContentsOfFile和imageWithContentsOfFile生成的Image對象,用來一次性展示,不可被緩存;Image對象可能被系統自動清理掉,並由系統自動加載;
2、如果需要緩存該Image對象,慎重選擇UIImage的初始化方法。