内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

栏目: IOS · 发布时间: 6年前

内容简介:这么用,在重点测试的界面,多操作,然后退出。重复几次。确认系统缓存已初始化。 退出重点测的界面后,开内存图, 如果内存释放的干净,就没什么 retain cycle 等内存泄漏。
内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

这么用,

在重点测试的界面,多操作,然后退出。

重复几次。确认系统缓存已初始化。 退出重点测的界面后,开内存图, 如果内存释放的干净,就没什么 retain cycle 等内存泄漏。

内存图自带断点效果,会暂停 app 的运行

可以看到此刻存在的所有对象。

环节短的循环引用,明显可见,找起来很快。

通过内存图,左边列表中,可以看到当前的所有对象,以及它们的数量。

最关心的就是感叹号,代表异常, 就是内存泄漏, 一般是 Retain Cycle

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

本文 Demo ,可见系统的代理 AppDelegate 实例, 相关 ViewController . 可看到图片视图有 24个。

中间大片的区域是对象的内存图,可看到他们是怎么关联的。可参考下

左边栏的右下方按钮,可以直接筛选出内存有错误的对象,方便找出内存泄漏的对象

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

可看出本文 Demo 内存泄漏严重。左边栏,点开几个带感叹号的,看情况。

右边栏,有一些具体信息

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

photo 照片模型对象,持有一个 location 位置的模型对象, location 位置的模型对象,持有一个对象,

那对象,又持有 photo 照片模型对象。

三个对象,构成了一个强引用的圈, retain cycle

发现问题了,解决就是改代码 很熟悉,直接改。

可以全局搜关键字,本文 demo 搜 .location

可以根据右边栏的信息找,

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

知道是哪个类,又有一个 closure 对象

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

可找到错误代码

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 的调试计量 工具 很强大,调试内存的时候,可切换调试视图层级等

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

左边栏的右上方的按钮,可以切换调试的选项, 内存转 UI, 内存转线程

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

通过使用 Xcode 内存图,内存泄漏少了很多。 重复操作三五次,又发现一个内存泄漏

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

对象结点很多,看图挺复杂的

可以用 Instruments 的 Leaks

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

Leaks 自带两个模版 Allocation 和 Leaks,

Allocation 模版对 app 运行过程中分配的所有对象的内存,都追踪到了。 上方的时间线展示了,已经分配了多少兆的内存。

All Heap & Anonymous VM, 所有堆上的内存,和虚拟内存 ( WWDC 2018/416 , 讲的比较详细)

下方的标记按钮,可以做分代标记

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

Leaks 模版会检查 app 所有的内存,找出泄漏的对象 ( 释放不了的对象 )

Instruments 的内存检查机制是,默认每隔 10 秒钟,自发的取一个内存快照分析

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

反复操作,找到第一个 Leaks, 可以暂停下

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

下方的 Leaks 详情表中,头部的 Leaks 按钮,有三个选项, 默认选项就是第一个, Leaks, 展示了所有内存泄漏的对象。

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

下方的右边栏就是更多信息,展示了详情界面每一列对象的进一步的资料

Leaks 详情表中,每一列对象,有一个灰色的箭头按钮,

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

点进去,可以看引用计数的增减日志

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

一般先看看第二个 Cycles & Roots, 又是一张内存图

photoModel 是循环圈的根结点,与左边的对象结点列表一致

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

有用的是第三个选项 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.

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

按住 Alt 键,点击方法名称左边的小三角,可以展开调用栈。

内存二三事: Xcode 内存图、Instruments 可视化检测循环引用

又看到了这个方法 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 堆的快照? )

方案见前文


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Host Your Web Site In The Cloud

Host Your Web Site In The Cloud

Jeff Barr / SitePoint / 2010-9-28 / USD 39.95

Host Your Web Site On The Cloud is the OFFICIAL step-by-step guide to this revolutionary approach to hosting and managing your websites and applications, authored by Amazon's very own Jeffrey Barr. "H......一起来看看 《Host Your Web Site In The Cloud》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

随机密码生成器
随机密码生成器

多种字符组合密码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具