iOS檢測UIView(自定義)釋放
阿新 • • 發佈:2018-12-24
今天在檢視工程時,遇到了一些問題,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
#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
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。