介绍
关于PLeakSniffer的介绍可以直接看作者的文章。下面是原文中的介绍。
子对象(比如view)建立一个对controller的weak引用,如果Controller被释放,这个weak引用也随之置为nil。那怎么知道子对象没有被释放呢?用一个单例对象每个一小段时间发出一个ping通知去ping这个子对象,如果子对象还活着就回一个pong通知。所以结论就是:如果子对象的controller已不存在,但还能响应这个ping通知,那么这个对象就是可疑的泄漏对象。
使用
介绍比较简单,作者给出的用法如下。
1 | #if MY_DEBUG_ENV |
实现
PLeakSnifferCitizen
1 | @protocol PLeakSnifferCitizen <NSObject> |
如果要对某个类型进行内存检查,这个对象要实现这个协议。下面看看库支持的三种类型的实现。
NSObject
NSObject
的分类实现了markAlive
方法。另外两个方法并没有实现。所以并不是所有继承自NSObject
的类型都会被追踪。主要是这个方法逻辑覆盖了三种库里支持的类型,所以在这里写,省得每个类型都写一遍。下面看看这个方法的具体逻辑
1 | - (BOOL)markAlive |
这个方法有点长,因为要分类型处理。主要做两个事情(有点违背方法的单一职责)。
- 如果不符合一些对象的alive定义,则直接返回false,告诉调用方,对象是非alive状态
- 如果对象是alive状态,添加监测代理,用于后续ping对象。
UIViewController
UIViewController
的分类实现了prepareForSniffer
和isAlive
。markAlive
通过继承NSObject
获得。
1 | + (void)prepareForSniffer |
prepareForSniffer
做了两个hook。
- hook controller的
present
方法,对present
出来的controller,调用markAlive
。 - Hook controller的
viewDidAppear
方法,对属性进行track
。
第二步,属性的track,其实就是递归调用属性的markAlive,添加proxy。
isAlive
的判断逻辑
- 所属view在UIWindow视图层级中
- 本身在navigation栈中,或者是由其他controller present出来。
主要代码
1 | UIView* v = self.view; |
UINavigationController
UINavigationController
只实现了prepareForSniffer
,isAlive
继承自UIViewController
。
1 | + (void)prepareForSniffer |
Hook push方法,对push的controller,调用markAlive。
UIView
UIView
继承自NSObject
,还需要实现prepareForSniffer
和isAlive
。跟前面几个的实现类似。hook一个合适的时机,调用markAlive。
1 | + (void)prepareForSniffer |
isAlive
的判断跟之前controller中isAlive的判断前半部分是一样的,通过查看view的最顶层view是不是UIWindow来判断alive。
PObjectProxy
proxy主要做两件事
- 注册通知接收ping的触发
- 检查宿主是否在不应存活时,还活着,通知出去,此处可能有内存泄漏
注册通知
1 | - (void)prepareProxy:(NSObject*)target { |
检查是否泄漏,可能泄漏就post出去
1 | - (void)detectSnifferPing |
流程
有了上边的基础设施,下面的流程就比较简单了。流程逻辑主要在PLeakSniffer
中。使用了一个timer,两个通知。从使用介绍来看,installLeakSniffer
是起点。
1 | - (void)installLeakSniffer { |
三个类型的prepare,以及启动ping定时。三个prepare逻辑上边分析过了,navigationController
和UIView
只是在合适的时间markAlive
。UIViewControler
除了在present时对presentingController进行标记,还需要对自己的属性进行递归标记。prepare流程结束后,所有之后的controller和View都会纳入监控(通过设置proxy)。
startPingTimer
会检查是否在主线程,如果不在主线程,dispatch到主线程。然后创建timer,每0.5秒调用一次sendPing
。sendPing
就是post一个通知。
1 | - (void)startPingTimer |
前面看过了proxy逻辑,这里sendPing的通知会到proxy中,proxy会检查是否有可能泄漏。如果有泄漏,会通过通知发出去。而接受的地方还是PLeakSniffer
。接收之后的处理比较简单。如果是需要被忽略的就丢弃。否则alert出来,活着print。
1 | - (void)detectPong:(NSNotification*)notif |
补充
前面提到UIViewController
的prepareSniffer
hook了viewDidAppear
方法,对属性进行track
。这个操作会减少遗漏,最大程度上找到所有可能出现内存问题的地方。但是属性量比较大,不知道这里会不会有内存问题。平常runtime用的少,这里看看别人的实践。
获取所有属性
1 | objc_property_t* properties = class_copyPropertyList(cls, &count ); |
判断属性是否是强引用
1 | bool isStrongProperty(objc_property_t property) |