原理分析在这里:http://www.cocoachina.com/ios/20160706/16951.html
我的代码分析:
入口函数,在application:didFinishLaunchingWithOptions中
if (DEBUG) {
[[PLeakSniffer sharedInstance] installLeakSniffer];
}
创建单例,进入到
- (void)installLeakSniffer {
[UINavigationController prepareForSniffer];
[UIViewController prepareForSniffer];
[UIView prepareForSniffer];
[self startPingTimer];
}
先调用各个类的prepareForSniffer方法,然后启动定时器,每隔0.5s发送一个Notif_PLeakSniffer_Ping的通知。
先分析UIViewController的prepareForSniffer方法
+ (void)prepareForSniffer{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleSEL:@selector(presentViewController:animated:completion:) withSEL:@selector(swizzled_presentViewController:animated:completion:)];
[self swizzleSEL:@selector(viewDidAppear:) withSEL:@selector(swizzled_viewDidAppear:)];
});
}
- (void)swizzled_presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion{
[self swizzled_presentViewController:viewControllerToPresent animated:flag completion:completion];
// modal出控制器时候执行
[viewControllerToPresent markAlive];
}
交换方法,hook住控制器被modal的那种形态,此外UINavigationController的prepareForSniffer其实就是hook住控制器被push的那种形态,总之会确保控制器无论以哪种形式出现,都能在出现那一刻调用markAlive方法。
// 控制器、View、Model等都会进入此处
- (BOOL)markAlive
{
if ([self pProxy] != nil) {
return false;
}
// 过滤系统自带的类
NSString* className = NSStringFromClass([self class]);
if ([className hasPrefix:@"_"] || [className hasPrefix:@"UI"] || [className hasPrefix:@"NS"]) {
return false;
}
// 对View类型,必需保证父视图是存在的
if ([self isKindOfClass:[UIView class]]) {
UIView* v = (UIView*)self;
if (v.superview == nil) {
return false;
}
}
// 对控制器,需保证该控制器的容器栈存在
if ([self isKindOfClass:[UIViewController class]]) {
UIViewController* c = (UIViewController*)self;
if (c.navigationController == nil && c.presentingViewController == nil) {
return false;
}
}
// 忽略类型,例如不需要监控的单例对象
static NSMutableDictionary* ignoreList = nil;
@synchronized (self) {
if (ignoreList == nil) {
ignoreList = @{}.mutableCopy;
NSArray* arr = @[@"UITextFieldLabel", @"UIFieldEditor", @"UITextSelectionView",
@"UITableViewCellSelectedBackground", @"UIView", @"UIAlertController"];
for (NSString* str in arr) {
ignoreList[str] = @":)";
}
}
if ([ignoreList objectForKey:NSStringFromClass([self class])]) {
return false;
}
}
// 创建一个PObjectProxy对象,将其绑定到自身
PObjectProxy* proxy = [PObjectProxy new];
[self setPProxy:proxy];
// 让proxy的target属性指向自身,注意target是一个weak对象,避免循环引用
[proxy prepareProxy:self];
return true;
}
PObjectProxy对象主要是监听PLeakSniffer单例不断发出的通知,不断调用以下方法
- (void)detectSnifferPing{
if (self.weakTarget == nil) return;
if (_hasNotified) return;
// 不断调用self.weakTarget属性指向对象的isAlive方法,若连续返回五次false,则认为可能存在内存泄漏
BOOL alive = [self.weakTarget isAlive];
if (alive == false) {
_leakCheckFailCount ++;
}
if (_leakCheckFailCount >= kPObjectProxyLeakCheckMaxFailCount) {
[self notifyPossibleMemoryLeak];
}
}
方法的关键是如何判断target指向对象是否销毁,关键方法是isAlive的实现。
以下是UIViewController的isAlive方法
- (BOOL)isAlive{
BOOL alive = true;
BOOL visibleOnScreen = false;
UIView* v = self.view;
while (v.superview != nil) { // 找到该控制器对象View的顶层视图
v = v.superview;
}
if ([v isKindOfClass:[UIWindow class]]) {
visibleOnScreen = true;
}
BOOL beingHeld = false;
if (self.navigationController != nil || self.presentingViewController != nil) {
beingHeld = true;
}
// visibleOnScreen为NO,说明该控制器视图已经不在当前窗口之上
// beingHeld为NO,说明该控制器已经被pop或者dismiss
// 两个条件都是NO,说明该控制器已经被pop或者dismiss,且不在窗口之上,但是尚未销毁,存在内存泄漏
if (visibleOnScreen == false && beingHeld == false) {
alive = false;
}
return alive;
}
对控制器来说,控制器拥有PObjectProxy对象,若控制器销毁,则PObjectProxy对象会被销毁,不能再监听来自单例PLeakSniffer的通知。若控制器未销毁,则PObjectProxy会不断监听通知,调用控制器的isAlive方法判断是否存在内存泄漏的可能,若有,则打印输出。这里对控制器内存泄漏的规则是:控制器已经被pop或者dismiss,且控制器的视图已不在窗口之上。
同理,对View来说, hook替换didMoveToSuperview方法,对每一个视图添加到父视图上时给它绑定PObjectProxy对象。
- (BOOL)isAlive{
BOOL alive = true;
BOOL onUIStack = false;
// 同控制器,判断视图是否在当前窗口之上
UIView* v = self;
while (v.superview != nil) {
v = v.superview;
}
if ([v isKindOfClass:[UIWindow class]]) {
onUIStack = true;
}
if (self.pProxy.weakResponder == nil) {
UIResponder* r = self.nextResponder;
while (r) {
if (r.nextResponder == nil) {
break;
}else{
r = r.nextResponder;
}
if ([r isKindOfClass:[UIViewController class]]) {
break;
}
}
// 该view的属性.pProxy.weakResponder弱引用了所属的控制器
self.pProxy.weakResponder = r;
}
// 若指向的控制器销毁了,self.pProxy.weakResponder会为nil
if (onUIStack == false && ![self.pProxy.weakResponder isKindOfClass:[UIViewController class]]) {
alive = false;
}
return alive;
}
UIView要想显示只能添加在控制器视图或UIWindow上。前者通过nextResponder查找,找到则使用弱指针指向该控制器(只需查找一次)。 后者通过superView递归查找顶层视图,如果某个时刻发现控制器不存在,且顶层视图不是UIWindow,那么判断存在泄露
用此框架,发现了一个第三方轮播图框架HYBLoopScrollView存在内存泄漏,仔细查看发现是setPageControlEnabled方法block中使用了self导致。。。