深刻淺出 RunLoop(五):RunLoop 與 NSTimer

RunLoop 系列文章

深刻淺出 RunLoop(一):初識
深刻淺出 RunLoop(二):數據結構
深刻淺出 RunLoop(三):事件循環機制
深刻淺出 RunLoop(四):RunLoop 與線程
深刻淺出 RunLoop(五):RunLoop 與 NSTimer
深刻淺出 RunLoop(六):相關面試題面試

RunLoop 與 NSTimer

  • 由前面的文章咱們知道,NSTimer是由RunLoop來管理的,NSTimer其實就是CFRunLoopTimerRef,他們之間是 toll-free bridged 的,能夠相互轉換;
  • 若是咱們在子線程上使用NSTimer,就必須開啓子線程的RunLoop,不然定時器沒法生效。

解決 tableview 滑動時 NSTimer 失效的問題

  • 問題:由前面的文章咱們知道,RunLoop同一時間只能運行在一種模式下,當咱們滑動tableview/scrollview的時候RunLoop會切換到UITrackingRunLoopMode界面追蹤模式下。若是咱們的NSTimer是添加到RunLoopKCFRunLoopDefaultMode/NSDefaultRunLoopMode默認模式下的話,此時是會失效的。
  • 解決:咱們能夠將NSTimer添加到RunLoopKCFRunLoopCommonModes/NSRunLoopCommonModes通用模式下,來保證不管在默認模式仍是界面追蹤模式下NSTimer均可以執行。
  • NSTimer的建立方式

若是咱們是經過如下方法建立的NSTimer,是默認添加到RunLoop的默認模式下的數據結構

[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"123");
    }];
複製代碼

咱們能夠經過如下方法建立NSTimer,來自定義添加到RunLoop的某種模式下多線程

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"123");
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
複製代碼

CFRunLoopAddTimer 函數實現

CFRunLoopAddTimer()函數中會判斷傳入的modeName模式名稱是否是 kCFRunLoopCommonModes通用模式,是的話就會將timer添加到RunLoop的 _commonModeItems 集合中,並同步該timer到 _commonModes 裏的全部模式中,這樣不管在默認模式仍是界面追蹤模式下NSTimer均可以執行。函數

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {       // 判斷 modeName 是否是 kCFRunLoopCommonModes
	    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	    if (NULL == rl->_commonModeItems) {    // 懶加載,判斷 _commonModeItems 是否爲空,是的話建立
	        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    	}
    	CFSetAddValue(rl->_commonModeItems, rlt);  // 將 timer 添加到 _commonModeItems 中
    	if (NULL != set) {
	        CFTypeRef context[2] = {rl, rlt};  // 將 timer 和 RunLoop 封裝到 context 中
    	    /* add new item to all common-modes */
            // 遍歷 commonModes,將 timer 添加到 commonModes 的全部模式下
    	    CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
    	    CFRelease(set);
    	}
    ......
	}
}


static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
    CFStringRef modeName = (CFStringRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
	CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
	CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
	CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
    }
}
複製代碼

NSTimer 和 CADisplayLink 存在的問題

  • 不許時:NSTimeCADisplayLink底層都是基於RunLoopCFRunLoopTimerRef的實現的,也就是說它們都依賴於RunLoop。若是RunLoop的任務過於繁重,會致使它們不許時。
    好比NSTimer每1.0秒就會執行一次任務,Runloop每進行一次循環,就會看一下NSTimer的時間是否達到1.0秒,是的話就執行任務。可是因爲Runloop每一次循環的任務不同,所花費的時間就不固定。假設第一次循環所花時間爲 0.2s,第二次 0.3s,第三次 0.3s,則再過 0.2s 就會執行NSTimer的任務,這時候可能Runloop的任務過於繁重,第四次花了0.5s,那加起來時間就是 1.3s,致使NSTimer不許時。
    解決方法:使用 GCD 的定時器。GCD 的定時器是直接跟系統內核掛鉤的,並且它不依賴於RunLoop,因此它很是的準時。示例以下:
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
    
    //建立定時器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //設置時間(start:幾s後開始執行; interval:時間間隔)
    uint64_t start = 2.0;    //2s後開始執行
    uint64_t interval = 1.0; //每隔1s執行
    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
    //設置回調
    dispatch_source_set_event_handler(timer, ^{
       NSLog(@"%@",[NSThread currentThread]);
    });
    //啓動定時器
    dispatch_resume(timer);
    NSLog(@"%@",[NSThread currentThread]);
    
    self.timer = timer;
/* 2020-02-01 21:34:23.036474+0800 多線程[7309:1327653] <NSThread: 0x600001a5cfc0>{number = 1, name = main} 2020-02-01 21:34:25.036832+0800 多線程[7309:1327705] <NSThread: 0x600001acb600>{number = 7, name = (null)} 2020-02-01 21:34:26.036977+0800 多線程[7309:1327705] <NSThread: 0x600001acb600>{number = 7, name = (null)} 2020-02-01 21:34:27.036609+0800 多線程[7309:1327707] <NSThread: 0x600001a1e5c0>{number = 4, name = (null)} */
複製代碼
  • 循環引用
相關文章
相關標籤/搜索