废话篇
最近忙于生活~~~很忙,很盲,很茻
如果一个全新的项目需要去检测项目内存泄漏
怎么做?
请见Swift之内存泄漏检测。
NSTableMap也可以做主题色切换,可能还有其他更多的技巧...
实战篇
swizzling无感知入侵
请见代码:
SEPLeakDetector.h
//
// SEPLeakDetector.h
// account-module-ios
//
// Created by Rudy.Li on 2018/9/3.
//
#import
@interface SEPLeakDetector : NSObject
#pragma mark - 单例方法.
+(SEPLeakDetector *) sharedInstance;
@end
@interface UINavigationController (SEPHook)
+ (void)hookUINavigationController_push;
+ (void)hookUINavigationController_pop;
@end
@interface UIViewController (SEPHook)
+ (void)hookUIViewController_push;
+ (void) hookUIViewController_present;
+ (void) hookUIViewController_dismiss;
@end
@interface UIView (SEPHook)
+ (void)hookUIView_moveToSuperView;
@end
SEPLeakDetector.m
//
// SEPLeakDetector.m
// account-module-ios
//
// Created by Rudy.Li on 2018/9/3.
//
#import "SEPLeakDetector.h"
#import
#import
#import "SEPToast.h"
@interface SEPLeakDetector()
@property (nonatomic) NSMapTable *trackingObjects;
@property (nonatomic) NSMapTable *globalTrackingObjects;//cached current be leaked UIViewControllers
@property (nonatomic,copy) NSArray *currentTrackingKeys; //cached old be leaked UIViewControllers
@property (nonatomic) NSMapTable *globalViewTrackingObjects;//cached current be leaked UIViews
@property (nonatomic,copy) NSArray *currentViewTrackingKeys; //cached old be leaked UIViews
@property (nonatomic,assign) BOOL isLoading;
@end
@implementation SEPLeakDetector
static SEPLeakDetector * sharedLocalSession = nil;
+ (void)load{
#ifdef DEBUG
[[SEPLeakDetector sharedInstance] setup];
#endif
}
+(SEPLeakDetector *) sharedInstance{
@synchronized(self){
if (sharedLocalSession == nil) {
sharedLocalSession = [[self alloc] init];
}
}
return sharedLocalSession;
}
- (void)setup{
_trackingObjects = [NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableWeakMemory];
_globalTrackingObjects = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory];
_globalViewTrackingObjects = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory];
[UIView hookUIView_moveToSuperView];
[UIViewController hookUIViewController_push];
[UIViewController hookUIViewController_present];
[UIViewController hookUIViewController_dismiss];
[UINavigationController hookUINavigationController_push];
[UINavigationController hookUINavigationController_pop];
}
- (void) expectDeallocate:(NSObject*)obj isDismiss:(BOOL)dismiss{
NSString *objectDescription = obj.description;
// 键:对象地址+方法名 值:对象
NSString *pointObjString = [NSString stringWithFormat:@"%@", objectDescription];
BOOL isExistGlobalTracking = [[SEPLeakDetector sharedInstance].currentTrackingKeys containsObject:pointObjString];
if (isExistGlobalTracking == NO) {
[_trackingObjects setObject:obj forKey:pointObjString];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (dismiss) {
NSArray *_currentArray = [SEPLeakDetector sharedInstance].currentTrackingKeys;
NSArray *_globalArray = [[SEPLeakDetector sharedInstance] globalTrackingObjects].keyEnumerator.allObjects;
NSMutableArray *_leakArray = [NSMutableArray array];
for (NSString *_globalString in _globalArray) {
@autoreleasepool {
BOOL result = NO;
for (NSString *_currentString in _currentArray) {
if ([_globalString isEqualToString:_currentString]) {
result = YES;
break;
}
}
if (result == NO) {
[_leakArray addObject:_globalString];
}
}
}
//NSLog(@"%@",_leakArray);
if ([_leakArray count]>0) {
NSString *str = [NSString stringWithFormat:@"present内存泄漏警告\r\n%@",_leakArray];
[[UIApplication sharedApplication] sep_showToastWithText:[NSString stringWithFormat:@"内存泄漏警告\r\n%@",str]];
return ;
}
}
BOOL didDeallocate = [self.trackingObjects objectForKey:pointObjString] == nil;
if (!didDeallocate) {
// 弹出警告框,提示异常信息
[[UIApplication sharedApplication] sep_showToastWithText:[NSString stringWithFormat:@"内存泄漏警告\r\n%@",pointObjString]];
return ;
}
//check subview was leaked
NSArray *_currentArray = [SEPLeakDetector sharedInstance].currentViewTrackingKeys;
NSArray *_globalArray = [[SEPLeakDetector sharedInstance] globalViewTrackingObjects].keyEnumerator.allObjects;
NSMutableArray *_leakArray = [NSMutableArray array];
for (NSString *_globalString in _globalArray) {
@autoreleasepool {
BOOL result = NO;
for (NSString *_currentString in _currentArray) {
if ([_globalString isEqualToString:_currentString]) {
result = YES;
break;
}
}
if (result == NO) {
[_leakArray addObject:_globalString];
}
}
}
if ([_leakArray count]>0) {
// 弹出警告框,提示异常信息
[[UIApplication sharedApplication] sep_showToastWithText:[NSString stringWithFormat:@"subViews内存泄漏警告\r\n%@",_leakArray]];
}
});
}
@end
@implementation UINavigationController (SEPHook)
+ (void)hookUINavigationController_push{
Method pushMethod = class_getInstanceMethod([self class], @selector(pushViewController:animated:));
Method hookMethod = class_getInstanceMethod([self class], @selector(hook_pushViewController:animated:));
method_exchangeImplementations(pushMethod, hookMethod);
}
- (void)hook_pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
NSString *popDetailInfo = [NSString stringWithFormat: @"%@ - %@ - %@", NSStringFromClass([self class]), @"push", NSStringFromClass([viewController class])];
NSLog(@"%@", popDetailInfo);
[SEPLeakDetector sharedInstance].currentViewTrackingKeys = [[SEPLeakDetector sharedInstance] globalViewTrackingObjects].keyEnumerator.allObjects;
[self hook_pushViewController:viewController animated:animated];
}
+ (void)hookUINavigationController_pop{
Method popMethod = class_getInstanceMethod([self class], @selector(popViewControllerAnimated:));
Method hookMethod = class_getInstanceMethod([self class], @selector(hook_popViewControllerAnimated:));
method_exchangeImplementations(popMethod, hookMethod);
}
- (nullable UIViewController *)hook_popViewControllerAnimated:(BOOL)animated{
[[SEPLeakDetector sharedInstance] expectDeallocate:[self.viewControllers lastObject] isDismiss:NO];
return [self hook_popViewControllerAnimated:animated];
}
@end
@implementation UIViewController (SEPHook)
+ (void)hookUIViewController_push{
Method pushMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
Method hookMethod = class_getInstanceMethod([self class], @selector(hook_ViewDidLoad));
method_exchangeImplementations(pushMethod, hookMethod);
}
- (void)hook_ViewDidLoad{
@synchronized (self) {
[SEPLeakDetector sharedInstance].isLoading = YES;
NSString *objectDescription = self.description;
// 键:对象地址+方法名 值:对象
NSString *pointObjString = [NSString stringWithFormat:@"%@", objectDescription];
[[[SEPLeakDetector sharedInstance] globalTrackingObjects] setObject:self forKey:pointObjString];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@synchronized (self) {
[SEPLeakDetector sharedInstance].isLoading = NO;
}
});
[self hook_ViewDidLoad];
}
+ (void) hookUIViewController_present{
Method popMethod = class_getInstanceMethod([self class], @selector(presentViewController:animated:completion:));
Method hookMethod = class_getInstanceMethod([self class], @selector(hook_presentViewController:animated:completion:));
method_exchangeImplementations(popMethod, hookMethod);
}
- (void)hook_presentViewController:(UIViewController*)controller animated: (BOOL)flag completion: (void (^ __nullable)(void))completion{
if ([@"UIAlertController" isEqualToString:NSStringFromClass([self class])]) {
}
else{
[SEPLeakDetector sharedInstance].currentTrackingKeys = [[SEPLeakDetector sharedInstance] globalTrackingObjects].keyEnumerator.allObjects;
[SEPLeakDetector sharedInstance].currentViewTrackingKeys = [[SEPLeakDetector sharedInstance] globalViewTrackingObjects].keyEnumerator.allObjects;
}
[self hook_presentViewController:controller animated:flag completion:completion];
}
+ (void) hookUIViewController_dismiss{
Method dismissMethod = class_getInstanceMethod([self class], @selector(dismissViewControllerAnimated:completion:));
Method hookMethod = class_getInstanceMethod([self class], @selector(hook_dismissViewControllerAnimated:completion:));
method_exchangeImplementations(dismissMethod, hookMethod);
}
- (void)hook_dismissViewControllerAnimated: (BOOL)flag completion: (void (^ __nullable)(void))completion{
if ([@"UIApplicationRotationFollowingController" isEqualToString:NSStringFromClass([self class])]) {
}
else{
[[SEPLeakDetector sharedInstance] expectDeallocate:self isDismiss:YES];
}
[self hook_dismissViewControllerAnimated:flag completion:completion];
}
@end
@implementation UIView (SEPHook)
+ (void)hookUIView_moveToSuperView{
Method pushMethod = class_getInstanceMethod([self class], @selector(addSubview:));
Method hookMethod = class_getInstanceMethod([self class], @selector(hook_addSubview:));
method_exchangeImplementations(pushMethod, hookMethod);
}
- (void)hook_addSubview:(UIView*)view{
@synchronized (self) {
if ([[SEPLeakDetector sharedInstance] isLoading]) {
NSString *objectDescription = self.description;
if ([objectDescription rangeOfString:@"_"].location == NSNotFound) {
NSString *pointObjString = [NSString stringWithFormat:@"%@", objectDescription];
[[[SEPLeakDetector sharedInstance] globalViewTrackingObjects] setObject:self forKey:pointObjString];
}
}
}
[self hook_addSubview:view];
}
@end
拷贝代码到项目无缝集成,即可检测内存泄漏了...
总结篇
1.本文仅基于UIViewController检测
2.泄漏的原因大部分是block循环引用
3.看到弹框见喜蛋