NSTimer 循環(huán)引用

先來一個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)引用問題

解決方案一

我們嘗試讓NSTimertarget弱引用就行了唄。開搞

// 讓 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)我們使用__weakself轉(zhuǎn)為弱指針的時候,這個只有在Block變臉捕獲的時候才生效。所以這里我們應(yīng)該使用NSTimerblock方法。

    // 讓 timer 對self 產(chǎn)生弱引用
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf test];
    }];

這次就可以成功了。

解決方案二

既然兩個對象都對彼此強引用,那么能不能找一個中間對象來解決這個問題呢。如下圖

在這里插入圖片描述

如圖所示VCTimer是強引用,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é)撒花???? 大家加油!??!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容