番外特别篇之 为什么我不建议你直接使用UIImage传值?--从一个诡异的相册九图连读崩溃bug谈起
关于"番外特别篇"
所谓"番外特别篇",就是系列文章更新期间内,随机插入的一篇文章.目前我正在更新的系列文章是 实现iOS图片等资源文件的热更新化.但是,这两天,被一个自己App中诡异的相册读取的Bug困扰,暂时延缓了文章的更新进度.这个BUG,诡异而又有趣,既然花了10个小时才理清,不妨再投入1个小时,晒出来供大家鉴赏,品玩.
Bug 的详细描述
诡异的画风
此Bug仅在操作多张高像素图片时才会触发,所谓高像素就是图片本身并不算大,但是图片宽高非常大的图片.这次触发这个问题的是一组 5701 * 3171 的图片.画风大家可以点击链接查看原图自行感受下 --https://github.com/ios122/why_not_uiimage/blob/master/bug_img.jpg?raw=true
当BOSS刚好是一个摄影爱好者
在大多数情况下,是很少有用户触发这个问题的,但是BOSS是一个摄影爱好者,手机里有许多高像素图,一天他想往自己公司的App上传分享几张图片时,他竟然没法把一次性地从相册选取九张图,每次选中后,点击"确定",都会理解Crash.是的,就是那九张图,其他图片是没问题的,8张图,也是OK的,他还强调了下是用的最新版本的App.
关于 BUG 的预处理
首先,我的第一反应是肯定是他的手机太烫了吧,重启下,就好了.恩,肯定是这样.发布作品的逻辑,好几个版本都没动过.模拟器,手机,我自己试了下,都是OK的.也没有其他用户反馈过,fabric也看不到任何log.对,手机太烫了.我稍后,再联系他,肯定就OK了.
稍后,再直接联系BOSS,竟然还是会Crash,他甚至给我录屏演示了一下,真的每次都会crash.而且我还无法复现.而且BOSS手机iPhone6 plus,自身内存不足的原因非常非常小.
形势,瞬间变得很紧张,这个问题的优先级瞬间被提到了最高!再次尝试了各种可能的情况.图片大小?它是9张1.5M的图,我就用9张3M的图,也是OK的呀!选取时,顺序有问题?我试着按照录屏中演示的顺序去选取图片,也是OK的.一股深深地无力感!竟然连复现都无法复现不了!
最后的最后,说是会拿手机给我测试.不过,最后BOSS的手机,还是没有拿到,只是拿到了开篇那张画风诡异的图片.没错,就是它,连续选取9张,就Crash了.
至少,我现在能复现问题了.下面的,需要的就只是时间,耐心还有大开的脑洞了.
Bug 分析思路的简要描述
我不觉得,分析Bug真的有什么思路可言.Bug产生的原因,是有许多可能性的,可能行验证的顺序,方式和深度很大程度上取决于coder本身已有的经验,天赋,甚至还有些许的运气!我能描述的,可能仅仅是我处理这个问题的一个相对的完整脑洞过程.部分分析过程间,明显不是有逻辑性的.越是诡异的问题,越是不能循规蹈矩,要时刻尝试去问自己最可能地问题是什么,而不是沿着一条路,一条道走到黑.
1.排除通用逻辑问题
Coder有些许高傲,有时候是有利于自己更冷静地处理问题的.稍微不自信点的童鞋,可能就会怀疑:我代码是不是有什么特殊的临界判断没有加?不行,我得去看看.一行一行,看代码,从天黑到天亮,从期待到绝望...其实,稍微有一些对比实验常识的人,都很容易猜到: 两种情况,唯一的变量是 图片素材本身,那 最可能 的原因肯定是 图片本身的问题.一种高大上的说法,这某种程度上,也暗合了所谓的"贪心算法".每次,都只从最可能的原因入手,管他谁是谁,我的代码就算有问题,那触发这个问题的可能性,也是远小于 图片素材本身的.---多么朴素的真理呀!
2.确定是相册选取图片内存过高
这个问题,在真机上,并不好确定,因为连续读取9张高像素图时,内存是瞬间飙升的,你几乎没有机会去观察内存占用,给人一种因为某种逻辑判断而导致的Crash的错觉.如果换做模拟器,会很容易看到,这个内存占用,是飙升到G单位的.当然,我也没那么睿智,我是单个N个断点,最终确认了Crash的代码的准确位置.一个for循环,每次step 1,这下很明显地看到内存,几乎是 100M/张的速度在飙升,而图片本身的大小只有 1.5M/张.此处我想说的是,打断点也是有技巧的,最后没有办法的办法也是讲究办法的.可是试着注释掉可能的引起的代码,然后逐步放开注释,这要观察,会比直接打断点快些.--意会!
3.确定是PHImageManager 的问题requestImageForAsset:方法引起的高内存占用
当你通过注释法,配合断点,很容易就可以引起内存高占用的代码.此处,我的App中,是读取相册原图,用的是 PHImageManager 的 requestImageForAsset:targetSize:contentMode:options: resultHandler: 方法.此处接下来的解决思路,有大坑呀!你可能会想,是UIImage加载的问题吧?那就研究下UIImage渲染机制吧.然后1天过去了,等你学成归来,蓦然发现 PHImageManager 是一个系统方法,它加载的图片机制,你无力干涉!我可能运气比较好些吧,研究UIImage的渲染机制,想想都头疼,抱着试一试的态度,我google了下: PHImageManager requestImageForAsset memory high,然后第一条链接的第二个回答就是我要到答案: http://stackoverflow.com/questions/33274791/high-memory-usage-looping-through-phassets-and-calling-requestimageforasset
是的,我运气,似乎总是很好~
4.使用requestImageDataForAsset:替换的问题requestImageForAsset:
答案原文是:
I found that if i switch from
- requestImageForAsset:targetSize:contentMode:options:resultHandler:
to
- requestImageDataForAsset:options:resultHandler:
i will receive the image with the same dimension {5376, 2688} but the size in byte is much smaller. So the memory issue is solved.
hope this help !!
(note : [UIImage imageWithData:imageData] use this to convert NSData to UIImage)
简单说,就是用 - requestImageDataForAsset:options:resultHandler: 替换 requestImageForAsset:targetSize:contentMode:options:resultHandler: 就可以了,前者是直接返回二进制数据,不渲染.
但是,这里有一个可能不是问题的问题, 这个方法调用是位于一个名为第三方库 TZImagePickerController 内,我方便直接改吗? 我是直接给改了.此处,将来必成大患,以后再用到,肯定还会有相同问题,还不如直接把原来的实现直接替换掉.当然,这也是成本最小的方法.这个库,本身,已经在App内,深度定制和重写了,如果一些成熟的第三方库,这么做,最好先备份或备注下.
5.使用imageWithData:兼容原来的调用
为了和原来的Api接口调用兼容,用imageWithData:将NSData转换为 UIImage 传出,同时扩展方法,使支持同时传出 UIImage和原始的 NSData对象.传出NSData对象的原因是,是因为高像素图片,会引起一些列的问题,故事到此远远没有结束,详见衍生问题部分.
6.变更前后的代码对比
还是来段代码感受下吧,一码剩千言:
/*原来的代码*/
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:option resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
if (downloadFinined && result) {
result = [self fixOrientation:result];
if (completion) completion(result,info);
}
}];
/*优化后代码*/
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
UIImage * result = [UIImage imageWithData:imageData];
BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
if (downloadFinined && result) {
result = [self fixOrientation:result];
if (completion) completion(result,info,imageData);
}
}];
此类Bug的可能的通用解决思路
首先,我要说明下,我解决的思路和方式,很大程度上依赖也受限于我已有的经验,此处的解法,可能不是最优解,最多只能算是个通用解.说不定,将来等我再研究下渲染机制一类的技术,会有一个新的更简单的方法.欢迎大神补充!
未来遇到UIImage内存问题的童鞋,至少能从此处获取的一个至少验证可用的解决策略.
回到问题本身,用一句概括就是:永远不要直接传递UIImage对象.在需要传递UIImage的场景中,请使用图片名或者NSData二进制对代替.
衍生问题应用与解决
故事,真的还没有完结.从相册顺利读取这张诡异的高像素图后,我发现我没有办法将它上传,也无法在轮播图上,连续显示.简要概括如下.
无法直接以UIImage格式,连续把九张图保存到缓存目录
图片选取后,并不是立即上传的,为了能实现"重发"功能,需要在缓存目录保留副本.原来是将 UIImage 转换为 NSData写入.在此过程中,又一次引起了巨额的内存开销.解决方法,就是直接缓存原始获取的 NSData 的对象,而不要 NSData --> UIImage --> NSData.
无法直接以UIImage格式,连续在轮播图上显示九张图
此处对应的是一个本地大图预览功能,实现是在前一个页面把九张本地图的UIImage传递给轮播预览组件.此处的坑是: 把一个存放在 数组中的UIImage对象传递给 UIImageView的 image属性,当UIImageView加载到父视图时,会引起巨额的内存占用.原因初步猜测是 UIImage 对象显示到 UIImageView 会有一个特殊的耗费内存的操作,如果原始的 UIImage 对象一直存在,这一块内存那就无法释放.这一步,困扰了我很久很久,好几个小时!我真没想到,一个UIImage对象,竟然会二次引起高内存占用.最终的解决方法,就是在前一个页面传递 NSData数组,在赋值处,再使用imageWithData:转换为 UIImage.这样,内存使用基本没什么起伏.
或许,我应该研究下 一个UIImage对象,竟然会二次引起高内存占用 的原因.欢迎大神完善!
参考链接
番外特别篇之 为什么我不建议你直接使用UIImage传值?--从一个诡异的相册九图连读崩溃bug谈起的更多相关文章
- WEB安全番外第一篇--其他所谓的“非主流”漏洞:URL跳转漏洞与参数污染
一.URL跳转篇: 1.原理:先来看这段代码: <?php if(isset($_GET["url_redircetion_target"])){ $url_redirect ...
- .net core番外第一篇:Autofac的几种常见注入方式、生命周期和AOP
使用Autofac进行服务注册实践: 新建三个项目,分别是webapi项目 Wesky.Core.Autofac以及两个类库项目 Wesky.Core.Interface和Wesky.Core.Ser ...
- Python之路番外(第二篇):PYTHON基本数据类型和小知识点
一.基础小知识点 1.如果一行代码过长,可以用续行符 \换行书写 例子 if (signal == "red") and \ (car == "moving") ...
- WEB安全番外第二篇--明日之星介绍HTML5安全问题介绍
一.CORS领域问题: 1.CORS的介绍请参考:跨域资源共享简介 2.HTML5中的XHR2级调用可以打开一个socket连接,发送HTTP请求,有趣的是,上传文件这里恰恰是multi-part/f ...
- 《Drools7.0.0.Final规则引擎教程》番外实例篇——相同对象and List使用
前奏 群组(QQ:593177274)交流中有朋友提出一个问题,怎么实现两个相同对象的插入和比较?相信很多朋友也遇到类似的问题,于是抽时间为大家写一段实例代码,后续代码会同步到GitHub中.下面简单 ...
- 『配置』服务器搭建 Office Online Server2016 实现文档预览 番外 错误篇
安装一个或多个角色.角色服务或功能失败.找不到源文件.请再次尝试在新的“添加角色和功能”向导会话中安装角色.角色服务或功能,然后在向导的“确认”页中单击“指定备用源路径”以指定安装所需的源文件的有效位 ...
- 矢量切片(Vector tile)番外一:Proj4js
说明:番外篇是对正篇矢量切片(Vector tile)中提到的一些值得继续延伸的关注点继续进行探索和学习,所涉及的内容以解决实际问题为主要导向. 一.新的需求? 在完成了矢量切片的工作后,新的需求出现 ...
- 【GISER && Painter】矢量切片(Vector tile)番外一:Proj4js
说明:番外篇是对正篇矢量切片(Vector tile)中提到的一些值得继续延伸的关注点继续进行探索和学习,所涉及的内容以解决实际问题为主要导向. 一.新的需求? 在完成了矢量切片的工作后,新的需求出现 ...
- 给深度学习入门者的Python快速教程 - 番外篇之Python-OpenCV
这次博客园的排版彻底残了..高清版请移步: https://zhuanlan.zhihu.com/p/24425116 本篇是前面两篇教程: 给深度学习入门者的Python快速教程 - 基础篇 给深度 ...
随机推荐
- Asp.net web form url route使用总结
asp.net web form 使用URL路由 注不是mvc中的路由 一.前台控件使用路由,通过表达式生成url地址,注意给路由参数赋值,防止使用了其他路由表达式值方式1:<asp:Hyper ...
- 【腾讯Bugly干货分享】QQ电话适配iOS10 Callkit框架
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/58009392302e4725036142fc Dev Club 是一个交流移动 ...
- 控件UI性能调优 -- SizeChanged不是万能的
简介 我们在之前的“UWP控件开发——用NuGet包装自己的控件“一文中曾提到XAML的布局系统 和平时使用上的一些问题(重写Measure/Arrange还是使用SizeChanged?),这篇博文 ...
- 设计模式之美:Type Object(类型对象)
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Type Object 的经典介绍. 实现方式(二):Type Object 在游戏设计中的使用. 意图 允许在运行时动态灵活的 ...
- openwrt-智能路由器hack技术(2)---"网路信息监控和窃取"
openwrt-智能路由器hack技术(2)---"网路信息监控和窃取" 1 导读 PS:之前写的一个文章,现在发现结构内容排版不是太好,导致阅读体验太差,影响传播和SEO,所 ...
- [.net 面向对象程序设计进阶] (9) 序列化(Serialization) (一) 二进制流序列化
[.net 面向对象程序设计进阶] (9) 序列化(Serialization) (一) 二进制流序列化 本节导读: 在.NET编程中,经常面向对象处理完以后要转换成另一种格式传输或存储,这种将对 ...
- 开源项目asmjit——调用自定义方法demo以及windbg调试
asmjit是一个开源项目,使用它可以将代码即时的编译成机器码,也就是所谓的jit技术. 初次接触这个项目,编写了一个demo,学习它的使用方法. 现将编写的demo以及调试jit生成的机器码的过程总 ...
- [ZigBee] 4、ZigBee基础实验——中断
前言 上一篇介绍了CC2530的IO的基础知识,并用LED的控制来展示如何配置并控制GPIO的输出,用KEY状态的读取实验来展示如何读取GPIO的状态.从上一节的KEY状态读取的代码看出是采用轮训方式 ...
- jQuery为哪般去掉了浏览器检测
由于做HTML5相关的项目,许多前卫时髦的前端技术就需要考虑一下IE是否支持.要是在以前,可以很方便地调用jQuery的jQuery.browser来实现. If(jQuery.browser.msi ...
- 破解 失控神域 dat文件格式。
CrackHelper.loadByteArray('../unit_data.dat', function(b:ByteArray):void{ b.uncompress(); var s:Stri ...