iOS NSTimer循環(huán)引用的解決方案

前言

在使用NSTimer,如果使用不得當(dāng)特別會(huì)引起循環(huán)引用,造成內(nèi)存泄露。所以怎么避免循環(huán)引用問題,下面我提出幾種解決NSTimer的幾種循環(huán)引用。

原因

當(dāng)你在ViewController(簡稱VC)中使用timer屬性,由于VC強(qiáng)引用timer,timer的target又是VC造成循環(huán)引用。當(dāng)你在VC的dealloc方法中銷毀timer,
發(fā)現(xiàn)VC被pop,VC的dealloc方法沒走,VC在等timer釋放才走dealloc,timer釋放在dealloc中,所以引起循環(huán)引用。

解決方案

方案一

//1、類別中使用copy block的形式使timer只引用自己而不引用TimerViewController(可以釋放)
//如果在block里面直接調(diào)用self,還是會(huì)保留環(huán)的。因?yàn)閎lock對self強(qiáng)引用,self對timer強(qiáng)引用,timer又通過userInfo參數(shù)保留block(強(qiáng)引用block),這樣就構(gòu)成一個(gè)環(huán)block->self->timer->userinfo->block,所以要打破這個(gè)環(huán)的話要在block里面弱引用self。
- (NSTimer *)timerWithLFJBlockSupport{
    __weak __typeof(&*self)weakSelf = self;
    NSTimer *timer = [NSTimer lfj_scheduledTimerWithTimeInterval:1 repeats:YES block:^{
        [weakSelf countAddWith:28];
    }];
    return timer;
}
#import "NSTimer+LFJBlockSupport.h"

@implementation NSTimer (LFJBlockSupport)
/*
該方案主要要點(diǎn):
將計(jì)時(shí)器所應(yīng)執(zhí)行的任務(wù)封裝成"Block",在調(diào)用計(jì)時(shí)器函數(shù)時(shí),把block作為userInfo參數(shù)傳進(jìn)去。
userInfo參數(shù)用來存放"不透明值",只要計(jì)時(shí)器有效,就會(huì)一直保留它。
在傳入?yún)?shù)時(shí)要通過copy方法,將block拷貝到"堆區(qū)",否則等到稍后要執(zhí)行它的時(shí)候,該blcok可能已經(jīng)無效了。
計(jì)時(shí)器現(xiàn)在的target是NSTimer類對象,這是個(gè)單例,因此計(jì)時(shí)器是否會(huì)保留它,其實(shí)都無所謂。此處依然有保留環(huán),然而因?yàn)轭悓ο螅╟lass object)無需回收,所以不用擔(dān)心。
*/

+ (NSTimer*)lfj_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats block:(void(^)(void))block{
    
    return [self scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(lfj_blockSelector:) userInfo:[block copy] repeats:repeats];
}
+(void)lfj_blockSelector:(NSTimer *)timer{
    void(^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
}

@end

方案二

//2、使用系統(tǒng)block(不過只支持iOS 10以后)(可以釋放)
- (NSTimer *)timerWithsystemBlock{
    __weak __typeof(&(*self))weakSelf = self;
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf countAddWith:28];
    }];
    return timer;
}

方案三

//3、使用中間類弱引用(weak)target
- (NSTimer *)timerWithWeakTimer{
    NSTimer *timer = [LFJWeakTimer weak_lfjScheduledTimerWithTimeInterval:1 target:self selector:@selector(countAddWith:) userInfo:nil repeats:YES];
    return timer;
}
//3、使用中間類弱引用(weak)target
- (NSTimer *)timerWithProxyTimerTimer{
    NSTimer *timer = [LFJProxyTimer weak_lfjScheduledTimerWithTimeInterval:1 target:self selector:@selector(countAddWith:) userInfo:nil repeats:YES];
    return timer;
}
#import "LFJWeakTimer.h"
@interface LFJWeakTimer()
@property (nonatomic,weak)id target;

@end
@implementation LFJWeakTimer

- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE(""){
    [anInvocation invokeWithTarget:self.target];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""){
    //消息轉(zhuǎn)發(fā)(返回值methodSignature不能為空(調(diào)用這個(gè)方法的類沒有實(shí)現(xiàn)這個(gè)方法就會(huì)為空))
    return [self.target methodSignatureForSelector:aSelector];
}

- (void)dealloc{
    NSLog(@"weakTimer------釋放");
}


+ (NSTimer *)weak_lfjScheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    LFJWeakTimer *weakTimer = [LFJWeakTimer new];
    weakTimer.target = aTarget;
    return [NSTimer scheduledTimerWithTimeInterval:ti target:weakTimer selector:aSelector userInfo:userInfo repeats:yesOrNo];
    //NSTimer 持有---->weakTimer 弱引用--->aTarget(當(dāng)NSTimer 調(diào)用invalidate的時(shí)候釋放weakTimer)
    //NSTimer 的target 都是強(qiáng)引用(即使使用__weak __typeof(&*self)weakself = self  對weakself也是強(qiáng)引用,weakself也會(huì)和NSTimer引起循環(huán)引用)
}

@end







@interface LFJProxyTimer : NSProxy
+ (instancetype)proxyWithObjc:(id)object;
+ (NSTimer *)weak_lfjScheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
@end

#import "LFJProxyTimer.h"
@interface LFJProxyTimer()
@property (nonatomic,weak)id target;

@end
@implementation LFJProxyTimer
- (instancetype)initWithObjc:(id)object {
    
    self.target = object;
    return self;
}
//通過類方法創(chuàng)建創(chuàng)建
+ (instancetype)proxyWithObjc:(id)object{
    return [[self alloc] initWithObjc:object];
    
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:self.target];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    //消息轉(zhuǎn)發(fā)(返回值methodSignature不能為空(調(diào)用這個(gè)方法的類沒有實(shí)現(xiàn)這個(gè)方法就會(huì)為空))
    return [self.target methodSignatureForSelector:aSelector];
}

- (void)dealloc{
    NSLog(@"weakTimer------釋放");
}

+ (NSTimer *)weak_lfjScheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    LFJProxyTimer *proxyTimer = [LFJProxyTimer proxyWithObjc:aTarget];
    proxyTimer.target = aTarget;
    return [NSTimer scheduledTimerWithTimeInterval:ti target:proxyTimer selector:aSelector userInfo:userInfo repeats:yesOrNo];
    //NSTimer 持有---->weakTimer 弱引用--->aTarget(當(dāng)NSTimer 調(diào)用invalidate的時(shí)候釋放weakTimer)
    //NSTimer 的target 都是強(qiáng)引用(即使使用__weak __typeof(&*self)weakself = self  對weakself也是強(qiáng)引用,weakself也會(huì)和NSTimer引起循環(huán)引用)
}
@end

方案四

//4、在當(dāng)前類釋放前釋放NSTimer(在ViewController執(zhí)行dealloc前釋放timer(不推薦))
- (NSTimer *)timerWithNormal{
    objc_setAssociatedObject(self, &nameKey, @"1", OBJC_ASSOCIATION_ASSIGN);
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(countAddWith:) userInfo:@34 repeats:YES];
    return timer;
}
- (void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    NSString *dd = objc_getAssociatedObject(self, &nameKey);
    if (dd) {
        [self releaseTimer];
    }
}

- (void)dealloc{
    NSLog(@"釋放");
    [self releaseTimer];
}

- (void)restartTimer {
    [self startTimer];
}
- (void)releaseTimer{
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

demo

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

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

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