1. 程式人生 > >iOS檢測UIView(自定義)釋放

iOS檢測UIView(自定義)釋放

    今天在檢視工程時,遇到了一些問題,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中列印並清除記錄。



作者:恩來客
連結:https://www.jianshu.com/p/4e78e44e3094
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。