Runloop 詳解

Runloop 詳解

參考鏈接:

深入理解RunLoop

CFRunLoop

概念

runloop :是管理和處理事件和消息的對象帚湘。

  • 并提供了一個(gè)入口函數(shù)來執(zhí)行 event loop 的邏輯。

    線程執(zhí)行該函數(shù)后大诸,就會(huì)一直處于該函數(shù)內(nèi)部的接受消息->等待->處理的循環(huán)中捅厂,知道循環(huán)結(jié)束(傳入 quit 消息),函數(shù)返回恒傻。

  • iOS 中,提供了2個(gè) runloop 對象:

    • NSRunLoop:基于CFRunLoopRef的封裝建邓,提供了面向?qū)ο蟮?API盈厘,但是這些 api 不是線程安全的官边。

    • CFRunLoopRef:是在 CoreFoundation 框架內(nèi)的,提供了純 c 函數(shù)的 API注簿,這些 api 都是線程安全的契吉。

RunLoop 和線程

  • 線程:pthread_t 和 NSThread 是一一對應(yīng)的。
  • CFRunLoop 是基于 pthread 來管理的诡渴。

apple 不允許直接創(chuàng)建 RunLoop捐晶,只提供了2個(gè)自動(dòng)獲取的函數(shù):CFRunLoopGetMain()、CFRunLoopGetCurrent().

內(nèi)部實(shí)現(xiàn):

//全局的 Dictionary惑灵,key 是 pthread_t,value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;

//訪問 loosDic 時(shí)的鎖
static CFSpinLock_t loopsLock;

//獲取一個(gè) pthread 對應(yīng)的 RunLoop
CFRunLoopRef _CFRunLoopGet(pthread_t thread){
    OSSpinLockLock(&loopsLock);

    if(!loopsDic)
        {
        //第一次進(jìn)入,初始化全局 Dic眼耀,并先為主線程創(chuàng)建一個(gè) runloop
        loosDic = CFDictionaryCreateMutable();
        CFRunLoopRef mainLoop = _CFRunLoopCreate();
            CFDictionarySetValue(loopsDic,pthread_main_thread_np(),mainLoop);
    }

    //直接從 dic 里獲取
    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic,thread);

    if(!loop)
    {
        //取不到時(shí),new 一個(gè)
        loop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic,thread,loop);
    
        //注冊一個(gè)回調(diào)哮伟,當(dāng)線程銷毀時(shí)干花,順便也銷毀對應(yīng)的 runloop
        _CFSetTSD(...,thread,loop,__CFFinalizeRunLoop);
    }
    OSSpinLockUnLock(&oopsLock);
    return loop;
}

CFRunLoopRef CFRunLoopGetMain(){
    return _CFRunLoopGet(pthread_main_thread_np());
}

CFRunLoopRef CFRunLoopGetCurrent(){
    return _CFRunLoopGet(pthread_self());
}

結(jié)論:從上面的代碼可以看出,線程和 RunLoop 之間是一一對應(yīng)的池凄,其關(guān)系是保存在一個(gè)全局的 Dictionary 里。線程剛創(chuàng)建時(shí)并沒有 RunLoop谅辣,如果你不主動(dòng)獲取修赞,那它一直都不會(huì)有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí)勾邦,RunLoop 的銷毀是發(fā)生在線程結(jié)束時(shí)。你只能在一個(gè)線程的內(nèi)部獲取其 RunLoop(主線程除外)割择。

RunLoop 對外的接口

  • 在 CF 中關(guān)于 RunLoop 的有5個(gè)類:

    1. CFRunLoopRef:

    2. CFRunLoopModeRef

    3. CFRunLoopSourceRef:事件產(chǎn)生的地方

      Source有兩個(gè)版本:Source0 和 Source1眷篇。

      • Source0 只包含了一個(gè)回調(diào)(函數(shù)指針)荔泳,它并不能主動(dòng)觸發(fā)事件。

        處理如 UIEvent玛歌,CFSoket 等事件昧港。

        使用時(shí),你需要先調(diào)用 CFRunLoopSourceSignal(source)支子,將這個(gè) Source 標(biāo)記為待處理,然后手動(dòng)調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop值朋,讓其處理這個(gè)事件叹侄。

      • Source1 包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針)昨登,被用于通過內(nèi)核和其他線程相互發(fā)送消息趾代。這種 Source 能主動(dòng)喚醒 RunLoop 的線程.

        Mach port驅(qū)動(dòng)丰辣,CFMachport,CFMessagePort

    4. CFRunLoopTimerRef:基于時(shí)間的觸發(fā)器.<mark>NSTimer是對RunLoopTimer的封裝.

      它和 NSTimer 是toll-free bridged 的笙什,可以混用尿褪。其包含一個(gè)時(shí)間長度和一個(gè)回調(diào)(函數(shù)指針)。當(dāng)其加入到 RunLoop 時(shí),RunLoop會(huì)注冊對應(yīng)的時(shí)間點(diǎn)顿仇,當(dāng)時(shí)間點(diǎn)到時(shí)淘正,RunLoop會(huì)被喚醒以執(zhí)行那個(gè)回調(diào).

    5. CFRunLoopObserverRef:觀察者

      Cocoa框架中很多機(jī)制比如CAAnimation等都是由RunLoopObserver觸發(fā)的 .

      每個(gè) Observer 都包含了一個(gè)回調(diào)(函數(shù)指針)臼闻,當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí)鸿吆,觀察者就能通過回調(diào)接受到這個(gè)變化述呐〕痛荆可以觀測的時(shí)間點(diǎn)有以下幾個(gè):

      typedef CF_OPTIONS(CFOptionFlags,CFRunLoopActivity){
          KCFRunLoopEntry,//即將進(jìn)入 loop
          KCFRunLoopBeforeTimers,//即將處理 timer
          KCFRunLoopBeforeSources,//即將處理 source
          KCFRunLoopBeforeWaiting代虾,//即將進(jìn)入休眠
          KCFRunLoopAfterWaiting,//剛從休眠中喚醒
          KCFRunLoopExit//即將退出loop
      }
      
  • 一個(gè) RunLoop 包含若干個(gè) Mode激蹲,每個(gè) Mode 又包含若干個(gè) Source/Timer/Observer(統(tǒng)稱 mode item棉磨。同時(shí)一個(gè) mode item 也可被加入不同的 mode 中)学辱。

    每次調(diào)用 RunLoop 的主函數(shù)時(shí),只能指定其中一個(gè) Mode策泣,這個(gè)Mode被稱作 CurrentMode衙傀。

    如果需要切換 Mode,只能退出 Loop萨咕,再重新指定一個(gè) Mode 進(jìn)入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer任洞,讓其互不影響蓄喇。

    如果一個(gè) mode 中一個(gè) item 都沒有交掏,則 RunLoop 會(huì)直接退出妆偏,不進(jìn)入循環(huán)盅弛。

RunLoop 的 mode

  • NSDefaultRunLoopMode:默認(rèn)钱骂,空閑狀態(tài)

  • UITrackingRunLoopMode:ScrollView滑動(dòng)時(shí)

  • UIInitializationRunLoopMode:啟動(dòng)時(shí)

  • NSRunLoopCommonModes:Mode集合.

    <mark>Timer計(jì)時(shí)會(huì)被scrollView的滑動(dòng)影響的問題可以通過將timer添加到NSRunLoopCommonModes來解決.

    
    

涉及到 RunLoop 的技術(shù):

  • 系統(tǒng)級(jí):GCD挪鹏、mach kernel、block讨盒、pthread
  • 應(yīng)用層:NSTimer解取、UIEvent、Autorelease禀苦、NSObject(NSDelayPerform、NSThreadPerformAddition)遂鹊、CADisplayLink振乏、CATranstion秉扑、CAAinimation慧邮、NSPort、NSURLConnection误澳、AFNetworking(在開啟新線程中添加自己的 runloop 監(jiān)聽事件)

RunLoop 在 Main Thread 堆棧中所處的位置

堆棧最底層是start(dyld),往上依次是main脓匿,UIApplication(main.m) -> GSEventRunModal(Graphic Services) -> RunLoop(包含CFRunLoopRunSpecific淘钟,CFRunLoopRun陪毡,__CFRunLoopDoSouces0米母,__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION) -> Handle Touch Event

使用 RunLoop 案例

  • AFNetworking

使用NSOperation+NSURLConnection并發(fā)模型都會(huì)面臨NSURLConnection下載完成前線程退出導(dǎo)致NSOperation對象接收不到回調(diào)的問題毡琉。AFNetWorking解決這個(gè)問題的方法是按照官方的guid:
https://developer.apple.com...
上寫的NSURLConnection的delegate方法需要在connection發(fā)起的線程runloop中調(diào)用,于是AFNetWorking直接借鑒了Apple自己的一個(gè)Demohttps://developer.apple.com...的實(shí)現(xiàn)方法單獨(dú)起一個(gè)global thread桅滋,內(nèi)置一個(gè)runloop慧耍,所有的connection都由這個(gè)runloop發(fā)起,回調(diào)也是它接收芍碧,不占用主線程,也不耗CPU資源号俐。

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
     @autoreleasepool {
          [[NSThread currentThread] setName:@"AFNetworking"];

          NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
          [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
          [runLoop run];
     }
}

+ (NSThread *)networkRequestThread {
     static NSThread *_networkRequestThread = nil;
     static dispatch_once_t oncePredicate;
     dispatch_once(&oncePredicate, ^{
          _networkRequestThread =
          [[NSThread alloc] initWithTarget:self
               selector:@selector(networkRequestThreadEntryPoint:)
               object:nil];
          [_networkRequestThread start];
     });

     return _networkRequestThread;
}
  • TableView中實(shí)現(xiàn)平滑滾動(dòng)延遲加載圖片

    利用CFRunLoopMode的特性泌豆,可以將圖片的加載放到NSDefaultRunLoopMode的mode里吏饿,這樣在滾動(dòng)UITrackingRunLoopMode這個(gè)mode時(shí)不會(huì)被加載而影響到。(意思就是滾動(dòng)時(shí)猪落,不進(jìn)行圖片的下載操作贞远,額,蓝仲,這個(gè)設(shè)計(jì)不是太合理)

UIImage *downloadedImage = ...;
[self.avatarImageView performSelector:@selector(setImage:)
     withObject:downloadedImage
     afterDelay:0
     inModes:@[NSDefaultRunLoopMode]];
     

接到程序崩潰時(shí)的信號(hào)進(jìn)行自主處理例如彈出提示等

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
while (1) {
     for (NSString *mode in allModes) {
          CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
     }
}

異步測試

- (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout
{
     __block Boolean fulfilled = NO;
     void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) =
     ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
          fulfilled = block();
          if (fulfilled) {
               CFRunLoopStop(CFRunLoopGetCurrent());
          }
     };

     CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting);
     CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

     // Run!
     CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);

     CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
     CFRelease(observer);
     return fulfilled;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市官疲,隨后出現(xiàn)的幾起案子杂曲,更是在濱河造成了極大的恐慌袁余,老刑警劉巖咱揍,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颖榜,死亡現(xiàn)場離奇詭異,居然都是意外死亡掩完,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門且蓬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來欣硼,“玉大人恶阴,你說我怎么就攤上這事诈胜》胧拢” “怎么了焦匈?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵昵仅,是天一觀的道長。 經(jīng)常有香客問我摔笤,道長够滑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任彰触,我火速辦了婚禮,結(jié)果婚禮上寞冯,老公的妹妹穿的比我還像新娘渴析。我一直安慰自己吮龄,他們只是感情好俭茧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布漓帚。 她就那樣靜靜地躺著,像睡著了一般尝抖。 火紅的嫁衣襯著肌膚如雪毡们。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天衙熔,我揣著相機(jī)與錄音,去河邊找鬼搅荞。 笑死红氯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的痢甘。 我是一名探鬼主播喇嘱,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼者铜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起放椰,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤作烟,失蹤者是張志新(化名)和其女友劉穎庄敛,沒想到半個(gè)月后俗壹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體藻烤,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年怖亭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涎显。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡期吓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出倾芝,到底是詐尸還是另有隱情讨勤,我是刑警寧澤晨另,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布潭千,位于F島的核電站借尿,受9級(jí)特大地震影響刨晴,放射性物質(zhì)發(fā)生泄漏路翻。R本人自食惡果不足惜狈癞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一茂契、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掉冶,春花似錦真竖、人聲如沸儡蔓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽召锈。三九已至,卻和暖如春涨岁,著一層夾襖步出監(jiān)牢的瞬間拐袜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工蹬铺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秉撇。 一個(gè)月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓甜攀,卻偏偏與公主長得像琐馆,于是被迫代替她去往敵國和親规阀。 傳聞我的和親對象是個(gè)殘疾皇子瘦麸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飄金閱讀 983評論 0 4
  • http://www.cocoachina.com/ios/20150601/11970.html RunLoop...
    紫色冰雨閱讀 835評論 0 3
  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,438評論 0 13
  • 野生的小花 歡樂地隨風(fēng)生長 舒適地躺在溫柔的草地上 蝴蝶哼唱著曲調(diào) 穿梭在微小的國度里 大地生長著潔白的甜蜜 我途...
    伍月的晴空閱讀 168評論 0 0
  • 兄弟,有一句話還是要和你說的:“不要把別人想的那么好滋饲,但是厉碟,也不要把別人想的那么壞屠缭,都是凡人箍鼓』骼埽” ——奇點(diǎn)對王同學(xué)說
    為土澳大火祈福閱讀 476評論 0 0