Instruments - Leaks的使用

作者: Longshihua | 来源:发表于2019-08-06 10:59 被阅读1次
    内存泄露

    Memory that was allocated at some point, but was never released and is no longer referenced by your app. Since there are no references to it, there’s now no way to release it and the memory can’t be used again.

    内存泄露指当一个对象或变量在使用完成后没有释放掉,这个对象一直占用着这部分内存, 直到应用停止。如果这种对象过多,内存就会耗尽,程序会因没有内存被杀死,即crash。内存泄露问题在 C++, C 和 Objective-C的 MRC 中是比较普遍的问题.。ARC中内存泄露问题较少,但是由于开发者的不注意,同样会出现内存泄露,比如:

    • 两个对象相互强引用
    • 代理
    • block
    • 通知
    • KVO
    • 定时器

    注意:从理论上讲, 内存泄露是由对象或变量没有释放引起的, 但实践证明并非所有的未释放的对象或变量都会导致内存泄露, 这与硬件环境和操作系统系统环境有关。

    查找泄漏点

    在 Xcode 中, 共提供了两种工具帮助

    • Analyze

    静态分析工具: 可以通过Product ->Analyze菜单项启动

    Analyze主要分析以下四种问题:

    1、逻辑错误:访问空指针或未初始化的变量等;
    2、内存管理错误:如内存泄漏等;
    3、声明错误:从未使用过的变量;
    4、API调用错误:未包含使用的库和框架。

    这里使用Analyze静态分析查找出来的泄漏点,称之为"可疑泄漏点"。之所以称之为"可疑泄漏点",是因为这些点未必一定泄露,确认这些点是否泄露, 还要通过Instruments动态分析工具的 LeaksAllocations跟踪模板。 Analyze静态分析只是一个理论上的预测过程.

    • Instruments

    Instruments可以帮我们了解到应用程序使用内存的几个方面:

    • 全局内存使用情况(Overall Memory Use)

    从全局的角度监测应用程序的内存使用情况,捕捉非预期的或大幅度的内存增长

    • 内存泄露(Leaked memory)

    未被你的程序引用,同时也不能被使用或释放的内存

    • 废弃内存(Abandoned memory)

    被你的程序引用,但是没什么用的内存

    • 僵尸对象(Zombies)

    僵尸对象指的是对应的内存已经被释放并且不再会使用到,但是你的程序却在某处依然有指向它的引用。在 iOS 中有一个NSZombie机制,这个是为了内存调试的目的而设计的一种机制。在这个机制下,当你NSZombieEnabled为 YES 时,当一个对应的引用计数减为 0 时,这个对象不会被释放,当这个对象再收到任何消息时,它会记录一条warning,而不是直接崩溃,以方便我们进行程序调试。

    Leaks

    查找内存泄露的过程

    1、在Xcode中对当前的项目执行Profile (Command-I),并在打开的对话框中选择Leaks这个模板:

    屏幕快照 2019-08-05 下午2.17.15.png

    也可以通过Xcode->Open Developer Tool->Instrument启动Instruments

    屏幕快照 2019-08-05 下午3.41.10.png

    2、进入Instruments后,选择正确的设备和应用程序。打开界面如下

    屏幕快照 2019-08-05 下午2.27.48.png

    Instruments中,虽然选择了Leaks模板,但默认情况下也会添加Allocations模板。基本上凡是内存分析都会使用Allocations模板, 它可以监控内存分布情况。

    3、点击红色按钮运行应用程序,我们可以看到如下界面:

    屏幕快照 2019-08-05 下午2.39.44.png

    4、选择Leak Checks来查看内存泄露

    Leaks

    屏幕快照 2019-08-05 下午2.42.20.png

    其中,绿色勾表示运行正常,没有内存泄露,如果有泄露,会自动显示红色x

    注意:显示红色x并不代表一定就有内存泄露,而且并不一定每次操作都能看到正确定位内存泄露部分。因为ARC 时代更常见的内存泄露是循环引用导致的Abandoned memory,而 Leaks 工具只负责检测 Leaked memory,应用有限。

    Cycles & Reboots

    屏幕快照 2019-08-05 下午2.45.04.png

    Call Tree

    屏幕快照 2019-08-05 下午2.46.07.png

    Canll Tree部分

    • Separate By Thread

    线程分离,只有这样才能在调用路径中能够清晰看到占用CPU最大的线程

    • Invert Call Tree

    从上到下跟踪堆栈信息.这个选项可以快捷的看到方法调用路径最深方法占用CPU耗时,比如FuncA{FunB{FunC}},勾选后堆栈以C->B->A把调用层级最深的C显示最外面

    • Hide System Libraries

    这个就更有用了,勾选后耗时调用路径只会显示app耗时的代码,性能分析普遍我们都比较关系自己代码的耗时而不是系统的。基本是必选项,注意有些代码耗时也会纳入系统层级,可以进行勾选前后前后对执行路径进行比对会非常有用

    • Top Functions

    按耗时降序排列

    • Flatten Recursion(一般不选)

    选上它会将调用栈里递归函数作为一个入口

    简单的方式可以快速勾选右边Call Tree中Separate by Thread和Hide System Libraries两个选项

    实际Demo

    写一个简单的Demo来实际查看一下效果

    • 创建工程,在Main.storyboard中选择NavigationController作为根视图控制器,在ViewController上添加一个UITableView并设置delegate、dataSource
    • 添加一个DetailViewController,实现代理方法显示内容,点击cell进入详情页面
    import UIKit
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var tableView: UITableView!
        var titles: [String] = []
        var images: [String] = []
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    
            for i in 0..<30 {
                titles.append("cell\(i)")
                images.append("imageString")
            }
        }
    }
    
    extension ViewController: UITableViewDataSource {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return titles.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
            return cell
        }
    }
    
    extension ViewController: UITableViewDelegate {
        func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            cell.textLabel?.text = titles[indexPath.row]
            cell.imageView?.image = UIImage(named: images[indexPath.row])
        }
    
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            let vc = DetailViewController()
            self.navigationController?.pushViewController(vc, animated: true)
        }
    }
    
    • 添加一个新的Swift文件,创建PersonPet两个类
    import Foundation
    
    class Person {
        let name: String
        var pet: Pet?
    
        init(name: String) {
            self.name = name
        }
    }
    
    class Pet {
        let name: String
        var onwer: Person?
    
        init(name: String) {
            self.name = name
        }
    }
    
    • DetailViewController中实现循环引用,这里包括两个地方,一个是定时器的循环引用,一个是对象之间的循环引用
    import UIKit
    
    class DetailViewController: UIViewController {
        var jack:Person!
        var dog: Pet!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = UIColor.white
    
            jack = Person(name: "Jack")
            dog = Pet(name: "dog")
            jack.pet = dog
            dog.onwer = jack
        }
    
        deinit {
           print("deinit")
        }
    }
    

    运行Leaks来查看

    运行程序之后,点击进入详情来回几次即可

    Leaks模式

    屏幕快照 2019-08-05 下午5.57.16.png

    看不出什么很有用的信息

    Cycles & Reboots模式

    屏幕快照 2019-08-05 下午5.56.42.png

    这里还是非常明显的,简单清晰易读,PersonPet相互引用

    Call Tree模式

    屏幕快照 2019-08-05 下午5.56.57.png

    可以通过双击Symbol Name来定位代码,也可以选择对应的行,右键Reveal In Xcode

    Debug Memory Graph

    直接使用Xcode自带的Debug Memory Graph来查看内存情况。运行程序,点击cell来回操作几次,然后点击Debug Memory Graph

    屏幕快照 2019-08-05 下午4.40.55.png

    查看结果

    屏幕快照 2019-08-05 下午5.54.22.png

    可以明显的看到对象之间的循环引用

    第三方内存查找库

    FBRetainCycleDetectorfacebook开源的一个用来检测对象是否有强引用循环的静态库。

    MLeaksFinder 提供了内存泄露检测更好的解决方案。只需要引入MLeaksFinder,就可以自动在 App运行过程检测到内存泄露的对象并立即提醒,无需打开额外的工具,也无需为了检测内存泄露而一个个场景去重复地操作。MLeaksFinder 目前能自动检测UIViewControllerUIView对象的内存泄露,而且也可以扩展以检测其它类型的对象。

    MLeaksFinder 的使用很简单,参照 https://github.com/Zepo/MLeaksFinder,基本上就是把 MLeaksFinder 目录下的文件添加到你的项目中,就可以在运行时(debug 模式下)帮助你检测项目里的内存泄露了,无需修改任何业务逻辑代码,而且只在 debug 下开启,完全不影响你的 release 包。

    实现原理可以看MLeaksFinder:精准 iOS 内存泄露检测工具

    iOS内存泄漏自动检测工具PLeakSniffer

    推荐使用第三方库来监测内存泄漏,开发的时候快速定位,节约时间

    参考

    Memory Usage Performance Guidelines
    Profile your app’s memory usage
    Instruments Tutorial with Swift: Getting Started

    相关文章

      网友评论

        本文标题:Instruments - Leaks的使用

        本文链接:https://www.haomeiwen.com/subject/gsunrctx.html