NSTimer
我們常常用NSTimer做一些定時任務,代碼如下:
#import "YTimerVC.h"
@interface YTimerVC ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation YTimerVC
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeAction) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timeAction {
static int i = 0;
NSLog(@"%d", i++);
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
}
問題:當我們返回上個頁面的時候,返現dealloc方法並沒有走。造成ViewController內存泄漏。
循環引用圖
當我們放回上個頁面的時候,由於Timer添加到RunLoop執行,Timer不會釋放TimerVC的持有。
最簡單的方法一
在ViewController,視圖消失的時候,我們調用Timer 的invalidate,讓RunLoop釋放對Timer的持有,響應的Timer也就釋放對VC的持有
缺點:當我們在自定義View的視圖中也添加Timer定期器任務,我們要做什麼時候釋放呢?也許,可以再Timer屬性暴露給VC使用,這樣的代碼耦合度比較高,不建議使用。
方法二
顯然這種方式是不能打破,放回上一級,VC釋放掉。
@property (nonatomic, weak) NSTimer *timer;
高逼格方法:NSProxy
從右邊入手,如果我們能讓Timer持有VC變爲弱引用的話,那麼當返回上一級的時候就不會出現這種情況。
NSProxy的Apple文檔說明,簡單來說提供了幾個信息
- NSProxy是一個專門用來做消息轉發的類
- NSProxy是個抽象類,使用需自己寫一個子類繼承自NSProxy
- NSProxy的子類需要實現兩個方法methodSignatureForSelector 和 forwardInvocation
我們知道了解了OC中消息轉發的機制,那麼你肯定知道,當某個對象的方法找不到的時候,也就是最後拋出doesNotRecognizeSelector的時候
-
消息發送,從方法緩存中找方法,找不到去方法列表中找,找到了將該方法加入方法緩存,還是找不到,去父類裏重複前面的步驟,如果找到底都找不到那麼進入2。
-
動態方法解析,看該類是否實現了resolveInstanceMethod:和resolveClassMethod:,如果實現了就解析動態添加的方法,並調用該方法,如果沒有實現進入3。
-
消息轉發,這裏分二步
- 調用forwardingTargetForSelector:,看返回的對象是否爲nil,如果不爲nil,調用objc_msgSend傳入對象和SEL。
- 如果上面爲nil,那麼就調用methodSignatureForSelector:返回方法簽名,如果方法簽名不爲nil,調用forwardInvocation:來執行該方法
-
如果以上都沒有處理:doesNotRecognizeSelector
NSProxy代碼實現:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface YProxy : NSProxy
@property (nonatomic, weak) id target;
@end
NS_ASSUME_NONNULL_END
#import "YProxy.h"
@implementation YProxy
/**
objc_methodSend(id, SEL,...)
流程:resolveInstanceMethod/resolveClassMethod
如果沒有響應:forwardingTargetForSelector //轉發tagret,切記不能是self,會造成循環
如果設置爲Nil:methodSingnatureForSelector
-> forwardInvocation
*/
///先走這個:
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
///如果forwardingTargetForSelector 爲空的話,我們就用NSOjbect初始化一個init
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
///如果聲明一個null對象,並返回[invocation getReturnValue:&null]。讓null去響應這樣,就算我們不賦值taget 也不會出現崩潰現象
- (void)forwardInvocation:(NSInvocation *)invocation {
// [invocation invokeWithTarget:self.target];
void *null = NULL;
[invocation getReturnValue:&null];
}
@end
//controller代碼
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
YProxy *proxy = [YProxy alloc];
proxy.target = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:proxy selector:@selector(timeAction) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timeAction {
static int i = 0;
NSLog(@"%d", i++);
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
}
引用流程圖: