??NSTimer
使用不當(dāng)就會造成內(nèi)存泄漏,比如常見的使用方法:
//定義
@property (nonatomic, strong) NSTimer *timer;
//實(shí)現(xiàn)
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(showMsg) userInfo:nil repeats:YES];
//銷毀
-(void)dealloc
{
[self.timer invalidate];
self.timer = nil;
}
??由于NSTimer
會引用住self
变丧,而 self
又持有NSTimer
對象曼氛,所以形成循環(huán)引用喉前,dealloc
永遠(yuǎn)不會被執(zhí)行,timer
也永遠(yuǎn)不會被釋放轻专,造成內(nèi)存泄漏忆矛。
嘗試解決辦法:
1、把timer
改成弱引用
@property (nonatomic, weak) NSTimer *timer;
??雖然
self
對timer
是弱引用请垛,但是控制的delloc
方法的執(zhí)行依賴于timer
的invalidate
催训,timer
的invalidate
又依賴于控制器的delloc
方法,這是一個雞生蛋還是蛋生雞的問題宗收,依舊是循環(huán)引用漫拭;
2、使用__weak
??那換個思路能不能讓NSTimer
弱引用target
:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(showMsg) userInfo:nil repeats:YES];
??
weak
關(guān)鍵字適用于block
混稽,當(dāng)block
引用了塊外的變量時采驻,會根據(jù)修飾變量的關(guān)鍵字來決定是強(qiáng)引用還是弱引用,如果變量使用weak
關(guān)鍵字修飾匈勋,那block
會對變量進(jìn)行弱引用礼旅,如果沒有__weak
關(guān)鍵字,那就是強(qiáng)引用洽洁。
??但是NSTimer
的scheduledTimerWithTimeInterval:target
方法內(nèi)部不會判斷修飾target
的關(guān)鍵字痘系,所以這里傳self
和weakSelf
是沒區(qū)別的,其內(nèi)部會對target
進(jìn)行強(qiáng)引用诡挂,還是會產(chǎn)生循環(huán)引用碎浇。
3、 選擇合適的時機(jī)手動釋放timer
采用下面的方法解決循環(huán)引用:
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
}
}
??在某些情況下璃俗,這種做法是可以解決問題的奴璃,但是有時卻會引起其他問題,比如控制器
push
到下一個控制器城豁,viewDidDisappear
執(zhí)行后苟穆,timer
被釋放,此時再pop
回來唱星,timer
已經(jīng)不復(fù)存在了雳旅。所以,這種"方案"并不是合理的间聊。
??優(yōu)化上面的方法這個時候可以采用配對使用在 viewWillAppear
開timer
啟攒盈,在 viewWillDisappear
關(guān)閉timer
:
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
if (!self.timer) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(showMsg) userInfo:nil repeats:YES];
}
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
}
}
上面的方法只是維護(hù)起來比較麻煩
最終解決辦法
1、自定義category
用block
解決
??網(wǎng)上有一些封裝的比較好的block
的解決方案哎榴,思路無外乎是封裝一個NSTimer
的category
型豁,提供block
形式的接口:
#import <Foundation/Foundation.h>
@interface NSTimer (TimerBlock)
/**
分類解決NSTimer在使用時造成的循環(huán)引用的問題
@param interval 間隔時間
@param block 回調(diào)
@param repeats 用于設(shè)置定時器是否重復(fù)觸發(fā)
@return 返回NSTimer實(shí)體
*/
+ (NSTimer *)block_TimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;
@end
#import "NSTimer+TimerBlock.h"
@implementation NSTimer (TimerBlock)
+ (NSTimer *)block_TimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)())block repeats:(BOOL)reqeats{
return [self timerWithTimeInterval:interval target:self selector:@selector(blockSelector:) userInfo:[block copy] repeats:reqeats];
}
+ (void) blockSelector:(NSTimer *)timer{
void (^block)() = timer.userInfo;
if (block) {
block();
}
}
@end
??上述創(chuàng)建方式調(diào)用者是NSTImer
自己僵蛛,只是NSTimer
捕獲了參數(shù)block
。這樣我們在使用timer
時迎变,由于target
的改變充尉,就不再有循環(huán)引用了。
__weak typeof(self) weakSelf = self; //避免 block 強(qiáng)引用 self
self.timer = [NSTimer block_TimerWithTimeInterval:3 block:^{
// [weakSelf dosomething];
} repeats:YES];
??iOS10中衣形,定時器的API新增了
block
方法驼侠,實(shí)現(xiàn)原理與此類似,這里采用分類為NSTimer
添加了帶有block
參數(shù)的方法谆吴,而系統(tǒng)是在原始類中直接添加方法倒源,最終的行為是一致的。
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
2句狼、給self
添加中間件proxy
??引入一個對象proxy
相速,proxy
弱引用 self
,然后 proxy
傳入NSTimer
鲜锚。即self
強(qiáng)引用NSTimer
突诬,NSTimer
強(qiáng)引用 proxy
,proxy
弱引用 self
芜繁,這樣通過弱引用來解決了相互引用旺隙,此時不會形成環(huán)。
??定義一個繼承自NSObject
的中間代理對象FFProxy
骏令,ViewController
不持有timer
蔬捷,而是持有FFProxy
實(shí)例,讓FFProxy
實(shí)例來弱引用ViewController
榔袋,timer
強(qiáng)引用FFProxy
實(shí)例周拐,直接看代碼:
//FFProxy.h
@interface FFProxy : NSObject
+(instancetype)proxyWithTarget:(id)target;
@end
//FFProxy.m
#import "FFProxy.h"
@interface FFProxy()
@property (nonatomic ,weak) id target;
@end
@implementation FFProxy
+(instancetype)proxyWithTarget:(id)target
{
FFProxy *proxy = [[FFProxy alloc] init];
proxy.target = target;
return proxy;
}
//僅僅添加了weak類型的屬性還不夠,為了保證中間件能夠響應(yīng)外部self的事件凰兑,需要通過消息轉(zhuǎn)發(fā)機(jī)制妥粟,讓實(shí)際的響應(yīng)target還是外部self,這一步至關(guān)重要吏够,主要涉及到runtime的消息機(jī)制勾给。
-(id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
//ViewController.m
FFProxy *proxy = [FFProxy proxyWithTarget:self];
//將timer的target設(shè)置為proxy,proxy又弱引用了控制器锅知,其實(shí)最終還是調(diào)用了控制器的showMsg方法播急。
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:proxy selector:@selector(showMsg) userInfo:nil repeats:YES];
//銷毀
-(void)dealloc
{
[self.timer invalidate];
self.timer = nil;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
是什么?
??消息轉(zhuǎn)發(fā)售睹,簡單來說就是如果當(dāng)前對象沒有實(shí)現(xiàn)這個方法桩警,系統(tǒng)會到這個方法里來找實(shí)現(xiàn)對象。
??本文中由于當(dāng)前target
是FFProxy
昌妹,但是FFProxy
沒有實(shí)現(xiàn)showMsg
方法(當(dāng)然也不需要它實(shí)現(xiàn))捶枢,讓系統(tǒng)去找target
實(shí)例的方法實(shí)現(xiàn)沉噩,也就是去找ViewController
中的方法實(shí)現(xiàn)。
3柱蟀、使用NSProxy
類
使用iOS
的NSProxy
類,NSProxy
就是專門用來做消息轉(zhuǎn)發(fā)的蚜厉。
//FFWeakProxy.h
@interface FFWeakProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end
//FFWeakProxy.m
@interface FFWeakProxy()
@property (nonatomic ,weak)id target;
@end
@implementation FFWeakProxy
+ (instancetype)proxyWithTarget:(id)target {
//NSProxy實(shí)例方法為alloc
FFWeakProxy *proxy = [FFWeakProxy alloc];
proxy.target = target;
return proxy;
}
/**
這個函數(shù)讓重載方有機(jī)會拋出一個函數(shù)的簽名长已,再由后面的forwardInvocation:去執(zhí)行
為給定消息提供參數(shù)類型信息
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
/**
* NSInvocation封裝了NSMethodSignature,通過invokeWithTarget方法將消息轉(zhuǎn)發(fā)給其他對象昼牛。這里轉(zhuǎn)發(fā)給控制器執(zhí)行术瓮。
*/
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
Controller
里代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
// 這里的target又發(fā)生了變化
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[FFWeakProxy proxyWithTarget:self] selector:@selector(showMsg) userInfo:nil repeats:YES];
}
//銷毀
-(void)dealloc
{
[self.timer invalidate];
self.timer = nil;
}