前言
在使用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;
}
}