内容简介:这么用,在重点测试的界面,多操作,然后退出。重复几次。确认系统缓存已初始化。 退出重点测的界面后,开内存图, 如果内存释放的干净,就没什么 retain cycle 等内存泄漏。
这么用,
在重点测试的界面,多操作,然后退出。
重复几次。确认系统缓存已初始化。 退出重点测的界面后,开内存图, 如果内存释放的干净,就没什么 retain cycle 等内存泄漏。
内存图自带断点效果,会暂停 app 的运行
可以看到此刻存在的所有对象。
环节短的循环引用,明显可见,找起来很快。
通过内存图,左边列表中,可以看到当前的所有对象,以及它们的数量。
最关心的就是感叹号,代表异常, 就是内存泄漏, 一般是 Retain Cycle
本文 Demo ,可见系统的代理 AppDelegate 实例, 相关 ViewController . 可看到图片视图有 24个。
中间大片的区域是对象的内存图,可看到他们是怎么关联的。可参考下
左边栏的右下方按钮,可以直接筛选出内存有错误的对象,方便找出内存泄漏的对象
可看出本文 Demo 内存泄漏严重。左边栏,点开几个带感叹号的,看情况。
右边栏,有一些具体信息
photo 照片模型对象,持有一个 location 位置的模型对象, location 位置的模型对象,持有一个对象,
那对象,又持有 photo 照片模型对象。
三个对象,构成了一个强引用的圈, retain cycle
发现问题了,解决就是改代码 很熟悉,直接改。
可以全局搜关键字,本文 demo 搜 .location
可以根据右边栏的信息找,
知道是哪个类,又有一个 closure 对象
可找到错误代码
photoModel.location?.reverseGeocodedLocation(completion: { (locationModel) in self.photoLocationLabel.attributedText = photoModel.locationAttributedString(withFontSize: 14.0) }) } 复制代码
photoModel 有一个 location 的属性,location 持有一个匿名函数 closure. 这个 closure 又引用了 photoModel。
不知道这个 closure 有没有 retain 该 photoModel,
点进方法看, 这是一个逃逸闭包,赋给了 LocationModel 的 placeMarkCallback 属性,强引用
func reverseGeocodedLocation(completion: @escaping ((LocationModel) -> Void)) { if placemark != nil { completion(self) } else { // 查看 completion placeMarkCallback = completion if !placeMarkFetchInProgress { beginReverseGeocodingLocationFromCoordinates() } } } 复制代码
与 Xcode 内存图检查到的一致。
解决循环引用,一般加 weak
ARC , 自动引用记数, iOS 用来管理内存的。 循环引用,retain cycle, 是 ARC 搞不定的地方
一个对象的引用记数, 就是有多少个其他的对象,持有对他的引用。
( 就是有多少个其他的对象,有指针指向他)
当这个对象的引用计数为 0, iOS 的 ARC 内存机制知道这个对象不必存在了,会找一个合适的时机释放。
循环引用,多个对象相互引用,形成了一个圈( 引用的链路 )
循环引用,问题很严重,内存泄漏了 ( 打个比方: 你找 iOS 系统借了钱,少还一大截。人家系统没说什么, 心里都记着 )
加 weak, ARC 就明白了, ( 因为 weak 是弱引用,不会增加该对象的引用记数。 直接写,隐含了一个 strong 的语义,默认 retain , 该对象的引用记数 + 1 )
链路就断了,内存回收成功。
Swift 的 closure 中,可以添加一个弱引用列表。 这个捕获列表可以让指定的属性弱引用。 closure 使用弱引用,就好
func reverseGeocode(locationForPhoto photoModel: PhotoModel) { photoModel.location?.reverseGeocodedLocation(completion: { [ weak photoModel] (locationModel) in self.photoLocationLabel.attributedText = photoModel?.locationAttributedString(withFontSize: 14.0) }) } 复制代码
Xcode 的调试计量 工具 很强大,调试内存的时候,可切换调试视图层级等
左边栏的右上方的按钮,可以切换调试的选项, 内存转 UI, 内存转线程
通过使用 Xcode 内存图,内存泄漏少了很多。 重复操作三五次,又发现一个内存泄漏
对象结点很多,看图挺复杂的
可以用 Instruments 的 Leaks
Leaks 自带两个模版 Allocation 和 Leaks,
Allocation 模版对 app 运行过程中分配的所有对象的内存,都追踪到了。 上方的时间线展示了,已经分配了多少兆的内存。
All Heap & Anonymous VM, 所有堆上的内存,和虚拟内存 ( WWDC 2018/416 , 讲的比较详细)
下方的标记按钮,可以做分代标记
Leaks 模版会检查 app 所有的内存,找出泄漏的对象 ( 释放不了的对象 )
Instruments 的内存检查机制是,默认每隔 10 秒钟,自发的取一个内存快照分析
反复操作,找到第一个 Leaks, 可以暂停下
下方的 Leaks 详情表中,头部的 Leaks 按钮,有三个选项, 默认选项就是第一个, Leaks, 展示了所有内存泄漏的对象。
下方的右边栏就是更多信息,展示了详情界面每一列对象的进一步的资料
Leaks 详情表中,每一列对象,有一个灰色的箭头按钮,
点进去,可以看引用计数的增减日志
一般先看看第二个 Cycles & Roots, 又是一张内存图
photoModel 是循环圈的根结点,与左边的对象结点列表一致
有用的是第三个选项 Call Tree , 调用树
与 Time Profiler 的 Call Tree 不一样,
Time Profiler 的 Call Tree 采集的是应用中所有的方法调用, Leaks 的 Call Tree 采集的是分配内存与内存泄漏相关的方法调用。
Call Tree 的选项一般勾选 Hide System Libraries 和 Separate by Thread. Hide System Libraries ,
隐藏系统的方法。系统的方法改不了,是黑盒,参考意义有限。
Separate by Thread. 将方法堆栈,按线程分开。一般出问题多在主线程,优先看 main thread.
按住 Alt 键,点击方法名称左边的小三角,可以展开调用栈。
又看到了这个方法 func reverseGeocode(locationForPhoto photoModel: PhotoModel)
再检查下
func reverseGeocode(locationForPhoto photoModel: PhotoModel) { photoModel.location?.reverseGeocodedLocation(completion: { [ weak photoModel] (locationModel) in self.photoLocationLabel.attributedText = photoModel?.locationAttributedString(withFontSize: 14.0) }) } 复制代码
self 是一个 CatPhotoTableViewCell 实例,self 持有 photoModel 属性,
( 函数里面的 photoModel, 使用的是 func updateCell(with photo: PhotoModel?) {
方法中传入的 self 的 photoModel 属性)
photoModel 持有 location 属性, location 属性持有一个逃逸闭包, 该逃逸闭包持有 self.
之前用 weak 处理了三对象的循环引用,现在有一个四对象的循环引用。
四对象的循环引用中 photoModel 在之前的处理中,已经弱引用了。本来好像没什么问题的。
估计系统没及时释放的 weak 的 photoModel,又泄漏了。
本文中,采用 Xcode 内存图,难以复现。有时候有。
解决就是再加一个 weak.
检查项目中的循环引用,通常使用分代式分析 ( Generational Analysis )
先记录一个内存使用的基线 A ( 当前使用场景, 建议用重点测的场景前的那一个 ),
进入一个场景 ( Controller 重点测的场景), 打个标 ( 记录现在的内存使用情况 ) B ,
再退出该场景,再打一个标 C。
如果 A < B , A = C , 正常,内存回收的不错。 如果 A < B <= C , 异常,内存很可能泄漏了
换句话,套路很简单,设立内存基线,点击进入新界面,(操作一下,滚一滚) 然后弹出,内存往往会先升后降。
这种操作,需要重复几次。找出必然。确认系统缓存已初始化,在运行。
( 有点类似苹果的单元测试算函数执行时间,跑一遍,就是运行了好几次的函数,取的平均值。 )
这里有一个很经典的面试题:
app 发布前,一般会系统检查循环引用,内存泄漏,怎么处理呢?
( 换个说法, 怎么分析 app 堆的快照? )
方案见前文
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 可视化Go内存管理
- 译文:Go 内存分配器可视化指南
- 基于Django+Bootstrap框架,可视化展示内存监控信息
- 遇见大数据可视化:来做一个数据可视化报表
- 遇见大数据可视化: 未来已来,变革中的数据可视化
- Python 数据可视化 2018:数据可视化库为什么这么多?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
见微知著-WEB用户体验解构
李清 / 机械工业出版社 / 2010-4 / 36.00元
本书用解构分析的方法,系统全面地介绍了Web页面设计的相关知识和要素。 本书从整体到局部地对网站的元素进行解构,包括网站整体布局、整体配色方案,到网站各个功能区域,如登录区、内容区、广告区等,最后到按钮、反馈、验证码、字体、文字语气等多个细节元素。本书通过解构这些元素来讲述如何对用户体验设计进行优化,如何进行搜索引擎优化。 本书适用于网站交互设计师、视觉设计师、产品经理、网站设计人员、......一起来看看 《见微知著-WEB用户体验解构》 这本书的介绍吧!