先來一個TimerDemo助助興。喲呵呵
定時器在項目開發(fā)中會經(jīng)常使用,下邊就是最簡單的一個定時器
@interface ViewController ()
// self 對 timer 強引用
@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會對target進行強引用,這里我們傳入的target就是當(dāng)前控制器,然而當(dāng)前控制器self中我們定義了一個timer的指針來強引用了timer,所以這兩個對象就造成了循環(huán)引用,如下圖
既然我們知道了循環(huán)引用的原因,那么我們就來解決一下這個循環(huán)引用問題
解決方案一
我們嘗試讓NSTimer對target弱引用就行了唄。開搞
// 讓 timer 對self 產(chǎn)生弱引用
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(test) userInfo:nil repeats:YES];
然并卵?。。?/code> 遺憾的是這個依然不能解決,原因就是當(dāng)我們使用__weak把self轉(zhuǎn)為弱指針的時候,這個只有在Block變臉捕獲的時候才生效。所以這里我們應(yīng)該使用NSTimer的block方法。
// 讓 timer 對self 產(chǎn)生弱引用
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf test];
}];
這次就可以成功了。
解決方案二
既然兩個對象都對彼此強引用,那么能不能找一個中間對象來解決這個問題呢。如下圖
如圖所示
VC對Timer是強引用,Timer對中間對象強引用,中間對象對VC產(chǎn)生弱引用。這也可以解決循環(huán)引用的問題。接下來就是我們需要創(chuàng)建一個中間對象,讓他對VC產(chǎn)生弱引用。
.h
@interface DDWeakObject : NSObject
/// 創(chuàng)建一個過渡類 讓NSTimer 或者 CADisplayLink 對這個類產(chǎn)生弱引用 解決循環(huá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ā)機制 讓這個類調(diào)用 target 的 方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
@end
我們在.m文件中定義一個target,讓這個自定義對象對target產(chǎn)生弱引用,所以我們使用weak修飾target這個屬性。我們這里使用消息轉(zhuǎn)發(fā)機制,讓該對象去響應(yīng)target的方法,有關(guān)消息轉(zhuǎn)發(fā)機制問題,請訪問我的另一篇文章 OC方法調(diào)用流程
// 當(dāng)前VC強引用了 timer
// timer 對 weakObject 強引用
// 但是 weakObject 對 self 是弱引用的關(guān)系
// 因此不會產(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這個類,這個類跟NSObject一樣是一個基類。專門處理這種交換的,好比一個中間橋梁。使用跟方案二差不多。
.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;
}
// 以下還是運用消息轉(zhuǎn)發(fā)機制進行
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
-(void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
與方案二的不同點在于,消息轉(zhuǎn)發(fā)時機不一樣,這里使用了是方法簽名。優(yōu)點在于NDProxy效率比方案二更高。
好了解決NSTimer定時器循環(huán)引用的方法已經(jīng)完成。后面兩個方案也可以解決CADisplayLink 產(chǎn)生的循環(huán)引用問題。因為CADisplayLink同樣是對target進行了強引用。這里我不在贅述。
最后附上TimerDemo,覺得不錯的,記得Star喲!
OK,完結(jié)撒花???? 大家加油!??!