原文 : 與佳期的我的博客(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/…