iOS进阶iOS性能调优OC-开发案例收集

Instruments - Leaks的使用

2019-08-06  本文已影响1人  Longshihua
内存泄露

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中内存泄露问题较少,但是由于开发者的不注意,同样会出现内存泄露,比如:

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

查找泄漏点

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

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

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

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

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

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

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

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

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

僵尸对象指的是对应的内存已经被释放并且不再会使用到,但是你的程序却在某处依然有指向它的引用。在 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部分

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

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

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

按耗时降序排列

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

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

实际Demo

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

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)
    }
}
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
    }
}
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

上一篇下一篇

猜你喜欢

热点阅读