最近在做一个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的初始化方法。

posted @ 2016-03-09 21:14 疯狂の小石子 阅读(7801) 评论(0) 推荐(1) 编辑
摘要: IOS开发中我们经常会用到模拟器调试,模拟器有个主要的好处就是程序启动块,最重要的是如果没有证书的话,我们就只能在模拟器上调试了。使用模拟器调试时我们可能碰到需要从系统相册选择图片的情况,特别是做图片处理类相关的程序时,更是经常用到。 初始化情况下模拟器中的相册中是空的,所以要想选择,我们就得... 阅读全文
posted @ 2015-12-11 13:38 疯狂の小石子 阅读(1766) 评论(0) 推荐(0) 编辑
摘要: 转载自http://altair21.com/156.html前提条件:XCode版本>=71. 进入xcode,菜单栏选择xcode –> preferences (快捷键 command + ,)在Accounts选项卡添加自己的Apple ID2. 在项目导航栏中选择要真机调试的项目,在工作区... 阅读全文
posted @ 2015-12-11 13:35 疯狂の小石子 阅读(2835) 评论(0) 推荐(0) 编辑
摘要: 【转自:GCD介绍(三): Dispatch Sources】何为Dispatch Sources 简单来说,dispatch source是一个监视某些类型事件的对象。当这些事件发生时,它自动将一个block放入一个dispatch queue的执行例程中。 说的貌似有点不清不楚。我们到底讨论... 阅读全文
posted @ 2014-05-07 15:49 疯狂の小石子 阅读(714) 评论(0) 推荐(0) 编辑
摘要: hh:mm:ss 按照12小时制的格式进行字符串格式化如果时间处于00:00:00——12:59:59,则返回的字符串正常如果时间处于13:00:00——23:59:59,则返回的字符串是实际时间-12小时后的值,也就是说比真实的时间少了12个小时。例如:14:00:00进行格式化后的字符串为“2:... 阅读全文
posted @ 2014-04-30 15:30 疯狂の小石子 阅读(55124) 评论(2) 推荐(0) 编辑
摘要: 1 + (NSString *)uuidString2 {3 CFUUIDRef uuid_ref = CFUUIDCreate(NULL);4 CFStringRef uuid_string_ref= CFUUIDCreateString(NULL, uuid_ref);5 NSString *uuid = [NSString stringWithString:(__bridge NSString *)uuid_string_ref];6 CFRelease(uuid_ref);7 CFRelease(uuid_string_ref);8 re... 阅读全文
posted @ 2014-04-02 15:25 疯狂の小石子 阅读(5768) 评论(0) 推荐(0) 编辑
摘要: + (UIImage *)createImageWithColor:(UIColor *)color{ CGRect rect=CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); UIGraphicsBeginImageContext(rect.size); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, [color CGColor]); CGContextFillRect(context, rect); UIImage... 阅读全文
posted @ 2014-03-31 17:14 疯狂の小石子 阅读(2288) 评论(0) 推荐(0) 编辑
摘要: [转载自:http://code4app.com/article/cocoapods-install-usage]目录CocoaPods是什么?如何下载和安装CocoaPods?如何使用CocoaPods?场景1:利用CocoaPods,在项目中导入AFNetworking类库场景2:如何正确编译运行一个包含CocoPods类库的项目CocoaPods是什么?当你开发iOS应用时,会经常使用到很多第三方开源类库,比如JSONKit,AFNetWorking等等。可能某个类库又用到其他类库,所以要使用它,必须得另外下载其他类库,而其他类库又用到其他类库,“子子孙孙无穷尽也”,这也许是比较特殊的情 阅读全文
posted @ 2014-03-12 22:01 疯狂の小石子 阅读(3144) 评论(0) 推荐(0) 编辑
摘要: [转载自:http://blog.csdn.net/yanghua_kobe/article/details/8395535] 前段时间关注过objc实现的AOP,在GitHub找到了其中的两个库:AOP-in-Objective-C和AOP-for-Objective-C。第一个是基于NSProxy来实现的;第二个是基于GCD以及block实现的。两者都使用了Cocoa的运行时编程技术,将拦截器注入给代理对象,使其干涉真是对象的执行顺序从而达到给代码增加“切面”的目的,这里的模式就是通常的代理模式。 因为时间关系,暂时只看了第一个库的代码,下面简短地分析一下。 NSProxy:如其名,... 阅读全文
posted @ 2014-03-11 17:46 疯狂の小石子 阅读(898) 评论(0) 推荐(0) 编辑
摘要: [转载自:http://mobile.51cto.com/iphone-274229.htm] Cocoa对象根类是本文要介绍的内容,仅凭Objective-C语言和运行环境并不足以构造哪怕是最简单的面向对象的程序,至少是不容易的。还缺少一些东西:即所有对象公有的基本行为和接口的定义。根类正是提供了这些定义。 之所以叫根类,是因为它位于整个类层次(这里是指Cocoa的类层次)的根上。根类不从其它类继承,但是类层次中的所有其它类都最终从根类继承下来。根类连同Objective-C语言,是Cocoa直接访问Objective-C运行环境或与之交互的基本途径。Cocoa对象的大部分对象行为能力都.. 阅读全文
posted @ 2014-03-11 17:31 疯狂の小石子 阅读(1012) 评论(0) 推荐(0) 编辑
点击右上角即可分享
微信分享提示