NSTimer是我們平時(shí)在項(xiàng)目中使用比較多的,但是使用的時(shí)候需要比較注意,需要在目標(biāo)對(duì)象釋放之前就要結(jié)束定時(shí)器,不然會(huì)造成內(nèi)存泄露。
具體原因就是NSTimer的常用的方法:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
這兩個(gè)方法需要設(shè)置target,但是系統(tǒng)內(nèi)部會(huì)對(duì)target強(qiáng)引用,導(dǎo)致target無(wú)法釋放。
有的時(shí)候我們希望定時(shí)器在目標(biāo)對(duì)象釋放的時(shí)候能夠自己結(jié)束,但是我們又無(wú)法直接修改系統(tǒng)內(nèi)部的api。
這個(gè)時(shí)候我們可以利用利用一種比較簡(jiǎn)單地思路來(lái)實(shí)現(xiàn):引入一個(gè)中間對(duì)象TimerDelegate,

image.png
將定時(shí)器的目標(biāo)對(duì)象設(shè)為TimerDelegate,再由TimerDelegate去調(diào)用原target的方法,這樣每次調(diào)用之前我們都可以知道target是否已經(jīng)被釋放了。
實(shí)現(xiàn):
新建對(duì)象TimerDelegate
// TimerDelegate.h
#import <Foundation/Foundation.h>
@interface TimerDelegate : NSObject
- (void)handleTarget:(id)aTarget selector:(SEL)aSel userInfo:(id)userInfo;
@property (weak,nonatomic)id desTarget; //注意弱引用
@property (assign,nonatomic)SEL selector;
@property (strong,nonatomic)id userInfo;
用runtime交換這兩個(gè)方法
// NSTimer+Release.m
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSArray *selNameArray = @[@"scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:",@"timerWithTimeInterval:target:selector:userInfo:repeats:"];
for (NSString *name in selNameArray) {
SEL oriSel = NSSelectorFromString(name);
NSString *newSelString = [NSString stringWithFormat:@"YT_%@",NSStringFromSelector(oriSel)];
SEL newSel = NSSelectorFromString(newSelString);
Class class = object_getClass((id)self);
[RuntimeManager swizzleClass:class OrigionMethod:oriSel withNewMethod:newSel];
}
});
}
如果定時(shí)器不重復(fù)則不需要處理
+ (NSTimer *)YT_scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
if(yesOrNo) {
TimerDelegate *timerDelegate = [[TimerDelegate alloc]init];
//將原目標(biāo)對(duì)象和參數(shù)傳遞給delegate
[timerDelegate handleTarget:aTarget selector:aSelector userInfo:userInfo];
SEL sel = NSSelectorFromString(@"fireTimer:");
//調(diào)用初始的方法,此處將定時(shí)器對(duì)象設(shè)置TimerDelegate, 然后調(diào)用TimerDelegate對(duì)象的fireTimer方法
NSTimer* timer = [NSTimer YT_scheduledTimerWithTimeInterval:ti target:timerDelegate selector:sel userInfo:userInfo repeats:yesOrNo];
return timer;
}
else {
return [self YT_scheduledTimerWithTimeInterval:ti target:aTarget selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
}
然后實(shí)現(xiàn)NStimerDelegate.m文件里的方法:
// NSTimerDelegate.m
- (void) fireTimer:(NSTimer *)timer {
if(!_desTarget) { //判斷原對(duì)象是否已經(jīng)釋放
[timer invalidate];
timer = nil;
return ;
}
//執(zhí)行原目標(biāo)對(duì)象的方法
if ([_desTarget respondsToSelector:self.selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.desTarget performSelector:self.selector withObject:timer];
#pragma clang diagnostic pop
}
}