移动开发俱乐部

iOS检测UIView(自定义)释放

2018-04-18  本文已影响46人  恩莱客

    今天在查看工程时,遇到了一些问题,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

几点说明:

  1. 方法eqx_exchangeInstanceMethod运行时替换掉系统方法。
  2. 唯一标签使用对象指针来记录。
  3. 通过NSUserDefaults来存储唯一标签,由于是测试代码,对效率没有进一步做考虑,慢就慢吧。
  4. didMoveToSuperview方法在addSubView:时也会调用,所以标签init时存了两次。
  5. eqx_didMoveToSuperview更慢,还加了循环,在主线程,也会卡,功能有限。
  6. 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中打印并清除记录。

上一篇下一篇

猜你喜欢

热点阅读