先來(lái)一個(gè)TimerDemo助助興绽乔。喲呵呵
定時(shí)器在項(xiàng)目開(kāi)發(fā)中會(huì)經(jīng)常使用,下邊就是最簡(jiǎn)單的一個(gè)定時(shí)器
@interface ViewController ()
// self 對(duì) timer 強(qiáng)引用
@property (nonatomic, strong) NSTimer *timer;
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
}
- (void)test {
NSLog(@"%s", __func__);
}
循環(huán)引用原因
由于NSTimer
會(huì)對(duì)target
進(jìn)行強(qiáng)引用,這里我們傳入的target
就是當(dāng)前控制器藏畅,然而當(dāng)前控制器self
中我們定義了一個(gè)timer
的指針來(lái)強(qiáng)引用了timer
,所以這兩個(gè)對(duì)象就造成了循環(huán)引用跟衅,如下圖
既然我們知道了循環(huán)引用的原因墨叛,那么我們就來(lái)解決一下這個(gè)循環(huán)引用問(wèn)題
解決方案一
我們嘗試讓NSTimer
對(duì)target
弱引用就行了唄殃恒。開(kāi)搞
// 讓 timer 對(duì)self 產(chǎn)生弱引用
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(test) userInfo:nil repeats:YES];
然并卵!S睾铩慕淡!
遺憾的是這個(gè)依然不能解決,原因就是當(dāng)我們使用__weak
把self
轉(zhuǎn)為弱指針的時(shí)候沸毁,這個(gè)只有在Block
變臉捕獲的時(shí)候才生效峰髓。所以這里我們應(yīng)該使用NSTimer
的block
方法。
// 讓 timer 對(duì)self 產(chǎn)生弱引用
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf test];
}];
這次就可以成功了息尺。
解決方案二
既然兩個(gè)對(duì)象都對(duì)彼此強(qiáng)引用携兵,那么能不能找一個(gè)中間對(duì)象來(lái)解決這個(gè)問(wèn)題呢。如下圖
如圖所示
VC
對(duì)Timer
是強(qiáng)引用掷倔,Timer
對(duì)中間對(duì)象
強(qiáng)引用眉孩,中間對(duì)象
對(duì)VC
產(chǎn)生弱引用。這也可以解決循環(huán)引用的問(wèn)題勒葱。接下來(lái)就是我們需要?jiǎng)?chuàng)建一個(gè)中間對(duì)象浪汪,讓他對(duì)VC
產(chǎn)生弱引用。
.h
@interface DDWeakObject : NSObject
/// 創(chuàng)建一個(gè)過(guò)渡類 讓NSTimer 或者 CADisplayLink 對(duì)這個(gè)類產(chǎn)生弱引用 解決循環(huán)引用的問(wèn)題
/// @param target 產(chǎn)生循環(huán)引用的target
+ (instancetype)weakObjectWithTarget:(id)target;
@end
.m
#import "DDWeakObject.h"
@interface DDWeakObject()
@property (weak, nonatomic) id target;
@end
@implementation DDWeakObject
+(instancetype)weakObjectWithTarget:(id)target {
DDWeakObject *weakObject = [[DDWeakObject alloc] init];
weakObject.target = target;
return weakObject;
}
// 利用消息轉(zhuǎn)發(fā)機(jī)制 讓這個(gè)類調(diào)用 target 的 方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
@end
我們?cè)?code>.m文件中定義一個(gè)target
,讓這個(gè)自定義對(duì)象對(duì)target
產(chǎn)生弱引用凛虽,所以我們使用weak
修飾target
這個(gè)屬性死遭。我們這里使用消息轉(zhuǎn)發(fā)
機(jī)制,讓該對(duì)象去響應(yīng)target
的方法凯旋,有關(guān)消息轉(zhuǎn)發(fā)機(jī)制問(wèn)題呀潭,請(qǐng)?jiān)L問(wèn)我的另一篇文章 OC方法調(diào)用流程
// 當(dāng)前VC強(qiáng)引用了 timer
// timer 對(duì) weakObject 強(qiáng)引用
// 但是 weakObject 對(duì) self 是弱引用的關(guān)系
// 因此不會(huì)產(chǎn)生循環(huán)引用
DDWeakObject *weakObject = [DDWeakObject weakObjectWithTarget:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakObject selector:@selector(test) userInfo:nil repeats:YES];
解決方案三
我們根據(jù)方案二钉迷,我們延伸出了NSProxy
這個(gè)類,這個(gè)類跟NSObject
一樣是一個(gè)基類钠署。專門處理這種交換的糠聪,好比一個(gè)中間橋梁。使用跟方案二差不多谐鼎。
.h
@interface DDProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end
.m
@interface DDProxy()
@property (weak, nonatomic) id target;
@end
@implementation DDProxy
+ (instancetype)proxyWithTarget:(id)target {
DDProxy *proxy = [DDProxy alloc];
proxy.target = target;
return proxy;
}
// 以下還是運(yùn)用消息轉(zhuǎn)發(fā)機(jī)制進(jìn)行
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
-(void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
與方案二的不同點(diǎn)在于舰蟆,消息轉(zhuǎn)發(fā)時(shí)機(jī)不一樣,這里使用了是方法簽名狸棍。優(yōu)點(diǎn)在于NDProxy
效率比方案二更高身害。
好了解決NSTimer定時(shí)器循環(huán)引用的方法已經(jīng)完成。后面兩個(gè)方案也可以解決CADisplayLink
產(chǎn)生的循環(huán)引用問(wèn)題草戈。因?yàn)?code>CADisplayLink同樣是對(duì)target
進(jìn)行了強(qiáng)引用塌鸯。這里我不在贅述。
最后附上TimerDemo唐片,覺(jué)得不錯(cuò)的丙猬,記得Star喲!
OK牵触,完結(jié)撒花???? 大家加油;吹俊!揽思!