Skip to content

Commit e3d92e3

Browse files
committed
Track leaked objects' lift circle
1 parent 29d5bbc commit e3d92e3

11 files changed

+156
-67
lines changed

MLeaksFinder.xcodeproj/project.pbxproj

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
B3667E4E1D38ADF6002B55DE /* MLeakedObjectProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = B3667E4D1D38ADF6002B55DE /* MLeakedObjectProxy.m */; };
1011
B367C8971C1C029500FDA5A9 /* NSObject+MemoryLeak.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B3ADE17B1C1BD2F000AA4F63 /* NSObject+MemoryLeak.h */; };
1112
B3ADE1701C1BD1D600AA4F63 /* MLeaksFinder.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B3ADE16F1C1BD1D600AA4F63 /* MLeaksFinder.h */; };
1213
B3ADE17A1C1BD29600AA4F63 /* UIView+MemoryLeak.m in Sources */ = {isa = PBXBuildFile; fileRef = B3ADE1791C1BD29600AA4F63 /* UIView+MemoryLeak.m */; };
@@ -34,6 +35,8 @@
3435
/* End PBXCopyFilesBuildPhase section */
3536

3637
/* Begin PBXFileReference section */
38+
B3667E4C1D38ADF6002B55DE /* MLeakedObjectProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLeakedObjectProxy.h; sourceTree = "<group>"; };
39+
B3667E4D1D38ADF6002B55DE /* MLeakedObjectProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLeakedObjectProxy.m; sourceTree = "<group>"; };
3740
B3ADE16C1C1BD1D600AA4F63 /* libMLeaksFinder.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMLeaksFinder.a; sourceTree = BUILT_PRODUCTS_DIR; };
3841
B3ADE16F1C1BD1D600AA4F63 /* MLeaksFinder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLeaksFinder.h; sourceTree = "<group>"; };
3942
B3ADE1781C1BD29600AA4F63 /* UIView+MemoryLeak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+MemoryLeak.h"; sourceTree = "<group>"; };
@@ -84,9 +87,13 @@
8487
B3ADE16E1C1BD1D600AA4F63 /* MLeaksFinder */ = {
8588
isa = PBXGroup;
8689
children = (
90+
B3667E4C1D38ADF6002B55DE /* MLeakedObjectProxy.h */,
91+
B3667E4D1D38ADF6002B55DE /* MLeakedObjectProxy.m */,
8792
B3ADE16F1C1BD1D600AA4F63 /* MLeaksFinder.h */,
8893
B3ADE17B1C1BD2F000AA4F63 /* NSObject+MemoryLeak.h */,
8994
B3ADE17C1C1BD2F000AA4F63 /* NSObject+MemoryLeak.m */,
95+
B3C512C31CE3368B0032C2C8 /* UIApplication+MemoryLeak.h */,
96+
B3C512C41CE3368B0032C2C8 /* UIApplication+MemoryLeak.m */,
9097
B3ADE1811C1BD54B00AA4F63 /* UINavigationController+MemoryLeak.h */,
9198
B3ADE1821C1BD54B00AA4F63 /* UINavigationController+MemoryLeak.m */,
9299
B3ADE1841C1BD5CC00AA4F63 /* UIPageViewController+MemoryLeak.h */,
@@ -99,8 +106,6 @@
99106
B3ADE1791C1BD29600AA4F63 /* UIView+MemoryLeak.m */,
100107
B3ADE17E1C1BD4B200AA4F63 /* UIViewController+MemoryLeak.h */,
101108
B3ADE17F1C1BD4B200AA4F63 /* UIViewController+MemoryLeak.m */,
102-
B3C512C31CE3368B0032C2C8 /* UIApplication+MemoryLeak.h */,
103-
B3C512C41CE3368B0032C2C8 /* UIApplication+MemoryLeak.m */,
104109
);
105110
path = MLeaksFinder;
106111
sourceTree = "<group>";
@@ -164,6 +169,7 @@
164169
B3C512C51CE3368B0032C2C8 /* UIApplication+MemoryLeak.m in Sources */,
165170
B3ADE17A1C1BD29600AA4F63 /* UIView+MemoryLeak.m in Sources */,
166171
B3ADE1861C1BD5CC00AA4F63 /* UIPageViewController+MemoryLeak.m in Sources */,
172+
B3667E4E1D38ADF6002B55DE /* MLeakedObjectProxy.m in Sources */,
167173
B3ADE17D1C1BD2F000AA4F63 /* NSObject+MemoryLeak.m in Sources */,
168174
B3ADE18C1C1BD74700AA4F63 /* UISplitViewController+MemoryLeak.m in Sources */,
169175
B3ADE1801C1BD4B200AA4F63 /* UIViewController+MemoryLeak.m in Sources */,

MLeaksFinder/MLeakedObjectProxy.h

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// MLeakedObjectProxy.h
3+
// MLeaksFinder
4+
//
5+
// Created by 佘泽坡 on 7/15/16.
6+
// Copyright © 2016 zeposhe. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
@interface MLeakedObjectProxy : NSObject
12+
13+
+ (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *)ptrs;
14+
+ (void)addLeakedObject:(id)object;
15+
16+
@end

MLeaksFinder/MLeakedObjectProxy.m

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// MLeakedObjectProxy.m
3+
// MLeaksFinder
4+
//
5+
// Created by 佘泽坡 on 7/15/16.
6+
// Copyright © 2016 zeposhe. All rights reserved.
7+
//
8+
9+
#import "MLeakedObjectProxy.h"
10+
#import "NSObject+MemoryLeak.h"
11+
#import <objc/runtime.h>
12+
#import <UIKit/UIKit.h>
13+
14+
static NSMutableSet *leakedObjectPtrs;
15+
16+
@interface MLeakedObjectProxy ()
17+
@property (nonatomic, weak) id object;
18+
@property (nonatomic, strong) NSNumber *objectPtr;
19+
@property (nonatomic, strong) NSArray *viewStack;
20+
@end
21+
22+
@implementation MLeakedObjectProxy
23+
24+
+ (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *)ptrs {
25+
static dispatch_once_t onceToken;
26+
dispatch_once(&onceToken, ^{
27+
leakedObjectPtrs = [[NSMutableSet alloc] init];
28+
});
29+
30+
if (!ptrs.count) {
31+
return NO;
32+
}
33+
if ([leakedObjectPtrs intersectsSet:ptrs]) {
34+
return YES;
35+
} else {
36+
return NO;
37+
}
38+
}
39+
40+
+ (void)addLeakedObject:(id)object {
41+
MLeakedObjectProxy *proxy = [[MLeakedObjectProxy alloc] init];
42+
proxy.object = object;
43+
proxy.objectPtr = @((uintptr_t)object);
44+
proxy.viewStack = [object viewStack];
45+
static const void *const kLeakedObjectProxyKey = &kLeakedObjectProxyKey;
46+
objc_setAssociatedObject(object, kLeakedObjectProxyKey, proxy, OBJC_ASSOCIATION_RETAIN);
47+
48+
[leakedObjectPtrs addObject:proxy.objectPtr];
49+
}
50+
51+
- (void)dealloc {
52+
NSNumber *objectPtr = _objectPtr;
53+
NSArray *viewStack = _viewStack;
54+
dispatch_async(dispatch_get_main_queue(), ^{
55+
[leakedObjectPtrs removeObject:objectPtr];
56+
57+
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Object Deallocated"
58+
message:[NSString stringWithFormat:@"%@", viewStack]
59+
delegate:nil
60+
cancelButtonTitle:@"OK"
61+
otherButtonTitles:nil];
62+
[alertView show];
63+
});
64+
65+
NSLog(@"Object deallocated.\nView-ViewController stack: %@", viewStack);
66+
}
67+
68+
@end

MLeaksFinder/NSObject+MemoryLeak.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
- (BOOL)willDealloc;
1616
- (void)willReleaseObject:(id)object relationship:(NSString *)relationship;
1717

18+
- (void)willReleaseChild:(id)child;
19+
- (void)willReleaseChildren:(NSArray *)children;
20+
1821
- (NSArray *)viewStack;
19-
- (void)setViewStack:(NSArray *)viewStack;
2022

2123
+ (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL;
2224

MLeaksFinder/NSObject+MemoryLeak.m

+53-8
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
//
88

99
#import "NSObject+MemoryLeak.h"
10+
#import "MLeakedObjectProxy.h"
1011
#import "MLeaksFinder.h"
1112
#import <objc/runtime.h>
1213
#import <UIKit/UIKit.h>
1314

1415
static const void *const kViewStackKey = &kViewStackKey;
16+
static const void *const kParentPtrsKey = &kParentPtrsKey;
1517
const void *const kLatestSenderKey = &kLatestSenderKey;
1618

1719
@implementation NSObject (MemoryLeak)
@@ -27,29 +29,60 @@ - (BOOL)willDealloc {
2729

2830
__weak id weakSelf = self;
2931
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
30-
[weakSelf assertNotDealloc];
32+
__strong id strongSelf = weakSelf;
33+
[strongSelf assertNotDealloc];
3134
});
3235

3336
return YES;
3437
}
3538

39+
- (void)assertNotDealloc {
40+
if ([MLeakedObjectProxy isAnyObjectLeakedAtPtrs:[self parentPtrs]]) {
41+
return;
42+
}
43+
[MLeakedObjectProxy addLeakedObject:self];
44+
45+
NSString *className = NSStringFromClass([self class]);
46+
NSString *message = [NSString stringWithFormat:@"Possibly Memory Leak.\nIn case that %@ should not be dealloced, override -willDealloc in %@ by returning NO.\nView-ViewController stack: %@", className, className, [self viewStack]];
47+
NSLog(@"%@", message);
48+
49+
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Memory Leak"
50+
message:[NSString stringWithFormat:@"%@", [self viewStack]]
51+
delegate:nil
52+
cancelButtonTitle:@"OK"
53+
otherButtonTitles:nil];
54+
[alertView show];
55+
}
56+
3657
- (void)willReleaseObject:(id)object relationship:(NSString *)relationship {
3758
if ([relationship hasPrefix:@"self"]) {
3859
relationship = [relationship stringByReplacingCharactersInRange:NSMakeRange(0, 4) withString:@""];
3960
}
4061
NSString *className = NSStringFromClass([object class]);
4162
className = [NSString stringWithFormat:@"%@(%@), ", relationship, className];
4263

43-
NSArray *viewStack = [self viewStack];
44-
[object setViewStack:[viewStack arrayByAddingObject:className]];
64+
[object setViewStack:[[self viewStack] arrayByAddingObject:className]];
65+
[object setParentPtrs:[[self parentPtrs] setByAddingObject:@((uintptr_t)self)]];
4566
[object willDealloc];
4667
}
4768

48-
- (void)assertNotDealloc {
49-
NSString *className = NSStringFromClass([self class]);
50-
NSString *message = [NSString stringWithFormat:@"Possibly Memory Leak.\nIn case that %@ should not be dealloced, override -willDealloc in %@ by returning NO.\nView-ViewController stack: %@", className, className, [self viewStack]];
51-
NSLog(@"%@", message);
52-
NSAssert(NO, message);
69+
- (void)willReleaseChild:(id)child {
70+
if (!child) {
71+
return;
72+
}
73+
74+
[self willReleaseChildren:@[ child ]];
75+
}
76+
77+
- (void)willReleaseChildren:(NSArray *)children {
78+
NSArray *viewStack = [self viewStack];
79+
NSSet *parentPtrs = [[self parentPtrs] setByAddingObject:@((uintptr_t)self)];
80+
for (id child in children) {
81+
NSString *className = NSStringFromClass([child class]);
82+
[child setViewStack:[viewStack arrayByAddingObject:className]];
83+
[child setParentPtrs:parentPtrs];
84+
[child willDealloc];
85+
}
5386
}
5487

5588
- (NSArray *)viewStack {
@@ -66,6 +99,18 @@ - (void)setViewStack:(NSArray *)viewStack {
6699
objc_setAssociatedObject(self, kViewStackKey, viewStack, OBJC_ASSOCIATION_COPY);
67100
}
68101

102+
- (NSSet *)parentPtrs {
103+
NSSet *parentPtrs = objc_getAssociatedObject(self, kParentPtrsKey);
104+
if (!parentPtrs) {
105+
parentPtrs = [[NSSet alloc] init];
106+
}
107+
return parentPtrs;
108+
}
109+
110+
- (void)setParentPtrs:(NSSet *)parentPtrs {
111+
objc_setAssociatedObject(self, kParentPtrsKey, parentPtrs, OBJC_ASSOCIATION_RETAIN);
112+
}
113+
69114
+ (NSSet *)classNamesInWhiteList {
70115
static NSSet *whiteList;
71116
static dispatch_once_t onceToken;

MLeaksFinder/UINavigationController+MemoryLeak.m

+1-10
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ - (void)swizzled_pushViewController:(UIViewController *)viewController animated:
4141
- (UIViewController *)swizzled_popViewControllerAnimated:(BOOL)animated {
4242
UIViewController *poppedViewController = [self swizzled_popViewControllerAnimated:animated];
4343

44-
// Navigation VC's rootViewController is the last VC in Navigation VC's viewControllers
4544
if (!poppedViewController) {
4645
return nil;
4746
}
@@ -86,15 +85,7 @@ - (BOOL)willDealloc {
8685
return NO;
8786
}
8887

89-
NSArray *viewStack = [self viewStack];
90-
91-
for (UIViewController *viewController in self.viewControllers) {
92-
NSString *className = NSStringFromClass([viewController class]);
93-
viewStack = [viewStack arrayByAddingObject:className];
94-
95-
[viewController setViewStack:viewStack];
96-
[viewController willDealloc];
97-
}
88+
[self willReleaseChildren:self.viewControllers];
9889

9990
return YES;
10091
}

MLeaksFinder/UIPageViewController+MemoryLeak.m

+1-7
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,7 @@ - (BOOL)willDealloc {
1818
return NO;
1919
}
2020

21-
NSArray *viewStack = [self viewStack];
22-
23-
for (UIViewController *viewController in self.viewControllers) {
24-
NSString *className = NSStringFromClass([viewController class]);
25-
[viewController setViewStack:[viewStack arrayByAddingObject:className]];
26-
[viewController willDealloc];
27-
}
21+
[self willReleaseChildren:self.viewControllers];
2822

2923
return YES;
3024
}

MLeaksFinder/UISplitViewController+MemoryLeak.m

+1-7
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,7 @@ - (BOOL)willDealloc {
1818
return NO;
1919
}
2020

21-
NSArray *viewStack = [self viewStack];
22-
23-
for (UIViewController *viewController in self.viewControllers) {
24-
NSString *className = NSStringFromClass([viewController class]);
25-
[viewController setViewStack:[viewStack arrayByAddingObject:className]];
26-
[viewController willDealloc];
27-
}
21+
[self willReleaseChildren:self.viewControllers];
2822

2923
return YES;
3024
}

MLeaksFinder/UITabBarController+MemoryLeak.m

+1-7
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,7 @@ - (BOOL)willDealloc {
1818
return NO;
1919
}
2020

21-
NSArray *viewStack = [self viewStack];
22-
23-
for (UIViewController *viewController in self.viewControllers) {
24-
NSString *className = NSStringFromClass([viewController class]);
25-
[viewController setViewStack:[viewStack arrayByAddingObject:className]];
26-
[viewController willDealloc];
27-
}
21+
[self willReleaseChildren:self.viewControllers];
2822

2923
return YES;
3024
}

MLeaksFinder/UIView+MemoryLeak.m

+1-7
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,7 @@ - (BOOL)willDealloc {
1818
return NO;
1919
}
2020

21-
NSArray *viewStack = [self viewStack];
22-
23-
for (UIView *view in self.subviews) {
24-
NSString *className = NSStringFromClass([view class]);
25-
[view setViewStack:[viewStack arrayByAddingObject:className]];
26-
[view willDealloc];
27-
}
21+
[self willReleaseChildren:self.subviews];
2822

2923
return YES;
3024
}

MLeaksFinder/UIViewController+MemoryLeak.m

+3-18
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,9 @@ - (BOOL)willDealloc {
5757
return NO;
5858
}
5959

60-
NSArray *viewStack = [self viewStack];
61-
62-
for (UIViewController *viewController in self.childViewControllers) {
63-
NSString *className = NSStringFromClass([viewController class]);
64-
[viewController setViewStack:[viewStack arrayByAddingObject:className]];
65-
[viewController willDealloc];
66-
}
67-
68-
UIViewController *presentedViewController = self.presentedViewController;
69-
if (presentedViewController) {
70-
NSString *className = NSStringFromClass([presentedViewController class]);
71-
[presentedViewController setViewStack:[viewStack arrayByAddingObject:className]];
72-
[presentedViewController willDealloc];
73-
}
74-
75-
NSString *className = NSStringFromClass([self.view class]);
76-
[self.view setViewStack:[viewStack arrayByAddingObject:className]];
77-
[self.view willDealloc];
60+
[self willReleaseChildren:self.childViewControllers];
61+
[self willReleaseChild:self.presentedViewController];
62+
[self willReleaseChild:self.view];
7863

7964
return YES;
8065
}

0 commit comments

Comments
 (0)