原文 : 与佳期的我的博客(gonghonglou.com)ios
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(nullable id)userInfo
repeats:(BOOL)yesOrNo;
复制代码
用此方法建立出来的计时器,会在指定的时间间隔以后执行任务。也能够令其反复执行任务,直到开发者稍后将其手动关闭为止。target 与 selector 参数表示计时器将在哪一个对象上调用哪一个方法。计时器会保留其目标对象,等到自身“失效”时再释放此对象。调用 invalidate 方法可令计时器失效;执行完相关任务以后,一次性的计时器也会失效。开发者若将计时器设置成重复执行模式,那么必须本身调用 invalidate 方法,才能令其中止。git
因为计时器会保留其目标对象,因此反复执行任务一般会致使应用程序出问题。也就是说,设置成重复执行模式的那种计时器,很容易引入“保留环”。github
这是《Effective Objective-C 2.0》书中”第 52 条:别忘了 NSTimer 会保留其目标对象“ 一章中的说法。苹果在其文档中的说明:macos
repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.bash
而且咱们在 Demo 中实验也确实如此,调用 + (NSTimer *)scheduledTimerWithTimeInterval:
方法时若是 repeats = NO 的话是没什么问题的,执行一次后 NSTimer 会自动 invalidate,但 repeats = YES 的话并不会,并且由于 NSTimer 的写法是这样的:函数
- (void)dealloc {
[_timer invalidate];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeatLog) userInfo:nil repeats:YES];
}
- (void)repeatLog {
NSLog(@"timer");
}
复制代码
self 持有 timer,timer 设置了 target 又会持有 self,形成循环引用,因此 dealloc 永远不会执行。反复执行任务则有可能出现崩溃。固然苹果在 iOS10 以后出了新的方法使用 block 的方式能够避免循环引用:oop
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block
API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
复制代码
但咱们总要兼容老版本,不多有 APP 会直接舍弃 iOS 10 以前的用户。因此《Effective Objective-C 2.0》书中也给出的解决方案是给 NSTimer 添加一个 Category,在 Category 里添加对 +scheduledTimerWithTimeInterval:
方法的封装方法,也就是想达到这样的效果:在 VC 中使用的时候,self 持有 timer,timer 持有 category(NSTimer 类对象),self 调用 timer 的时候传入 block 给 category 执行。这样就能避免循环引用了。ui
BlocksKit 也提供了一个 NSTimer+BlocksKit.m 分类实现了相同的功能spa
BlocksKit 最新的 tag(2.2.5)及以前的版本的实现是:code
@implementation NSTimer (BlocksKit)
+ (id)bk_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats
{
NSParameterAssert(block != nil);
return [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats];
}
+ (id)bk_timerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats
{
NSParameterAssert(block != nil);
return [self timerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats];
}
+ (void)bk_executeBlockFromTimer:(NSTimer *)aTimer {
void (^block)(NSTimer *) = [aTimer userInfo];
if (block) block(aTimer);
}
@end
复制代码
代码很简单,正如《Effective Objective-C 2.0》书中所讲的思路:
这段代码将计时器所应执行的任务封装成“块”,在调用的计时器函数时,把它做为 userInfo 参数传进去。该参数可用来存放“万能值”,只要计时器还有效,就会一直保留着它。传入参数时要经过 copy 方法将 block 拷贝到“堆”上,不然等到稍后要执行他的时候,该块可能已经无效了。计时器如今的 target 是 NSTimer 类对象,这是个单例,由于计时器是否会保留它,其实都无所谓。此处依然有保留环,然而由于类对象(class object)无须回收,因此不用担忧。
只须要在使用的时候注意避免 block 产生循环引用便可,用 __weak typeof(self) weakSelf = self;
,__strong typeof(weakSelf) strongSelf = weakSelf;
便可避免。
值得提一下的是 BlocksKit 当前的最新代码里的 NSTimer+BlocksKit.m 又有了不一样的实现,直接抛弃了 NSTimer,而是用了 CFRunLoopTimerCreateWithHandler 来实现:
@implementation NSTimer (BlocksKit)
+ (instancetype)bk_scheduleTimerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats usingBlock:(void (^)(NSTimer *timer))block
{
NSTimer *timer = [self bk_timerWithTimeInterval:seconds repeats:repeats usingBlock:block];
[NSRunLoop.currentRunLoop addTimer:timer forMode:NSDefaultRunLoopMode];
return timer;
}
+ (instancetype)bk_timerWithTimeInterval:(NSTimeInterval)inSeconds repeats:(BOOL)repeats usingBlock:(void (^)(NSTimer *timer))block
{
NSParameterAssert(block != nil);
CFAbsoluteTime seconds = fmax(inSeconds, 0.0001);
CFAbsoluteTime interval = repeats ? seconds : 0;
CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent() + seconds;
return (__bridge_transfer NSTimer *)CFRunLoopTimerCreateWithHandler(NULL, fireDate, interval, 0, 0, (void(^)(CFRunLoopTimerRef))block);
}
@end
复制代码
Demo 地址:GHLCrashGuard
小白出手,请多指教。如言有误,还望斧正!
转载请保留原文地址:gonghonglou.com/2019/07/07/…