今天在查看工程时,遇到了一些问题,ViewController已释放,但是其中的UIView没有释放,原因是定时器没有关闭造成的,突然有个念头:我能不能通过某种方法检测到这种情况。
我先监听init
方法,在检测dealloc
方法(由于工程使用的ARC内存管理,delloc不能直接替换,通过单独改为MRC也不是很好,毕竟是UIView的扩展类,使用了方法didMoveToSuperview
代替),怎么检测呢,此处通过runtime替换到这两个方法,什么时候执行呢,需要在init之前,可以使用NSObject的方法:
//加载时调用,启动时
+ (void)load;
//首次初始化前调用
+ (void)initialize;
由于是测试代码,没有进一步研究测试代码质量,此处选择的方法+ (void)load
,创建了一个类别UIView+Memory.h
,其.m文件代码如下:
#import "UIView+Memory.h"
#import "objc/runtime.h"
@implementation UIView (Memory)
void eqx_exchangeInstanceMethod(Class class, SEL originalSelector, SEL newSelector){
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method newMethod = class_getInstanceMethod(class, newSelector);
if(class_addMethod(class, originalSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
class_replaceMethod(class, newSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, newMethod);
}
}
+ (void)load {
eqx_exchangeInstanceMethod([self class], @selector(initWithFrame:), @selector(eqx_initWithFrame:));
//view加载、消失时都会调用didMoveToSuperview方法
eqx_exchangeInstanceMethod([self class], @selector(didMoveToSuperview), @selector(eqx_didMoveToSuperview));
}
- (instancetype)eqx_initWithFrame:(CGRect)frame{
id obj = [self eqx_initWithFrame:frame];
if ([self isCustomFunction]) {
// NSLog(@"%@(%p)_init", NSStringFromClass([self class]), &self);
#if 1
NSString *log = [NSString stringWithFormat:@"%@(%p)", NSStringFromClass([self class]), &self];
NSUserDefaults *initUser = [NSUserDefaults standardUserDefaults];
NSMutableArray *array = [[NSMutableArray alloc]initWithArray:[initUser objectForKey:@"initArray"]];
[array addObject:log];
[array addObject:log];
[initUser setObject:array forKey:@"initArray"];
[initUser synchronize];
#endif
}
return obj;
}
- (void)eqx_didMoveToSuperview{
if ([self isCustomFunction]) {
// NSLog(@"%@(%p)_release", NSStringFromClass([self class]), &self);
#if 1
dispatch_async(dispatch_get_main_queue(), ^{
NSString *log = [NSString stringWithFormat:@"%@", NSStringFromClass([self class])];
NSUserDefaults *initUser = [NSUserDefaults standardUserDefaults];
NSMutableArray *array = [[NSMutableArray alloc]initWithArray:[initUser objectForKey:@"initArray"]];
for (int i = 0; i < array.count; i++) {
if ([array[i] hasPrefix:log]) {
[array removeObjectAtIndex:i];
break;
}
}
[initUser setObject:array forKey:@"initArray"];
[initUser synchronize];
});
#endif
[self eqx_didMoveToSuperview];
}
}
- (BOOL)isCustomFunction{
NSBundle *mainB = [NSBundle bundleForClass:[self class]];
//比较沙盒
if (mainB == [NSBundle mainBundle]) {
//自定义类
return YES;
}else{
//系统类
return NO;
}
}
@end
几点说明:
- 方法
eqx_exchangeInstanceMethod
运行时替换掉系统方法。 - 唯一标签使用对象指针来记录。
- 通过NSUserDefaults来存储唯一标签,由于是测试代码,对效率没有进一步做考虑,慢就慢吧。
-
didMoveToSuperview
方法在addSubView:
时也会调用,所以标签init
时存了两次。 -
eqx_didMoveToSuperview
更慢,还加了循环,在主线程,也会卡,功能有限。 -
isCustomFunction
方法是用来区分View是否是自定义view,系统控件咱没考虑,以后慢慢优化吧。
什么时候获取到泄漏的views,控制器完全释放,刚开始说了,好像有时候控制器释放了,views并没有完全释放,头疼......
索性在进入控制器是打印下吧,把所有检测到的信息打印一下,以下写了一个控制器扩展类UIViewController+Memory.h
,.m文件:
#import "UIViewController+Memory.h"
#import "objc/runtime.h"
@implementation UIViewController (Memory)
void eqxx_exchangeInstanceMethod(Class class, SEL originalSelector, SEL newSelector){
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method newMethod = class_getInstanceMethod(class, newSelector);
if(class_addMethod(class, originalSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
class_replaceMethod(class, newSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, newMethod);
}
}
+ (void)load {
eqxx_exchangeInstanceMethod([self class], @selector(viewDidLoad), @selector(eqx_viewDidLoad));
}
- (void)eqx_viewDidLoad{
#if 1
NSUserDefaults *initUser = [NSUserDefaults standardUserDefaults];
NSArray *array = [initUser objectForKey:@"initArray"];
NSLog(@"array = %@", array);
#endif
[initUser removeObjectForKey:@"initArray"];
[initUser synchronize];
[self eqx_viewDidLoad];
}
@end
首先是替换了下viewDidLoad
方法在eqx_viewDidLoad
中打印并清除记录。