iOS 性能优化是一个比较复杂的问题, 其中之一就是内存泄露检测, 很多人会第一时间想到使用 Instruments. 由于学习成本比较高, 专业详细的教程也比较少, 在学习了基本介绍后就望而生畏了. 今天浏览了微信阅读团队的技术博客, 发现了一个非常友好的内存泄露检测库 https://github.com/Tencent/MLeaksFinder
MLeaksFinder
简单介绍一下 MLeaksFinder.
官方解释:
具体的方法是, 为基类 NSObject 添加一个方法 -willDealloc 方法, 该方法的作用是, 先用一个弱指针指向 self, 并在一小段时间 (3 秒) 后, 通过这个弱指针调用 -assertNotDealloc, 而 -assertNotDealloc 主要作用是直接中断言.
核心代码:
- - (BOOL)willDealloc {
- __weak id weakSelf = self;
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- [weakSelf assertNotDealloc];
- });
- return YES;
- }
- - (void)assertNotDealloc {
- NSAssert(NO, @"");
- }
这样, 当我们认为某个对象应该要被释放了, 在释放前调用这个方法, 如果 3 秒后它被释放成功, weakSelf 就指向 nil, 不会调用到 -assertNotDealloc 方法, 也就不会中断言, 如果它没被释放(泄露了),-assertNotDealloc 就会被调用中断言. 这样, 当一个 UIViewController 被 pop 或 dismiss 时(我们认为它应该要被释放了), 我们遍历该 UIViewController 上的所有 view, 依次调 -willDealloc, 若 3 秒后没被释放, 就会中断言.
例如在 UIViewController 的分类中, 使用 Method Swizzling http://nshipster.com/method-swizzling/ ,hook 掉了 viewDidDisappear:,viewWillAppear:,dismissViewControllerAnimated:completion: 等方法, 让他们都执行 willDealloc 方法, 这样, 在不入侵开发代码的情况下, 为 UIViewController 添加了检查内存泄露的功能(AOP).
安装
安装非常简单, 直接在 Podfile 中添加 pod 'MLeaksFinder', 你不需要在任何文件中引入头文件, 执行 pod install 后, 直接构建或者 run 一下就好了.
案例
在 iOS 中, 比较常见的内存泄露场景就是循环引用. 作为一个 iOS 工程师, 应该时刻警惕循环引用带来的问题. 然而在赶工或者稍有不慎的情况下, 还是会出现一些有问题的代码. 对于 Xcode 来说, 编译器会对编写代码中明显的循环引用进行提示, 比如对于 self.property 持有的 block 中, 使用 self,Xcode 就会显示警告. 但是, 对于以下代码, Xcode 就不会警告:
- LessonWithoutJobCell *cell = [tableView dequeueReusableCellWithIdentifier:reusedID1];
- if (!cell) {
- cell = [[LessonWithoutJobCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reusedID1];
- }
- cell.model = model;
- [cell setEnterBlock:^{
- if (self.classBlock) {
- self.classBlock(model);
- }
- }];
- return cell;
在 tableView 中的某一个 cell, 持有了一个 block, 在 block 中使用了 self.classBlock, 乍一看好像没有问题啊.
真的没有问题吗?
在这个控制器被 pop 后, MLeaksFinder 立刻就弹出了弹窗
IMG_6481.PNG
点击 Retain Cycle
IMG_6479.PNG
仔细想想, 真的是出现循环引用. 首先是 self 引用了 tableView,tableView 引用了 cell,cell 中的 block 引用了 self, 这样, 在控制器被 pop 之后, 这个 view 还存在着强引用, 这样它的内存就得不到释放, 这样就造成了循环引用, 经历频繁的 push 和 pop 操作后, 内存将会暴增. 其实这是一个很低级的 bug 了, 但是稍不注意, 就被忽略了.
既然发现了问题, 解决循环引用就非常简单了. 在 cell 的 block 外面, 把 self 定义为 weak 类型, 打破循环引用:
- ......
- __weak typeof(self) weakSelf = self;
- ......
- // 把 block 中的 self 换成 weakSelf 即可
- [cell setEnterBlock:^{
- if (weakSelf.classBlock) {
- weakSelf.classBlock(model);
- }
- }];
至此, MLeaksFinder 的简单使用就介绍完了, 真的十分简单就找到了一些潜在的问题, 对于工程几乎 0 入侵, 强烈推荐使用.
来源: https://juejin.im/entry/5c064362f265da6165015778