Runloop

Runloop

  • 多線程編程指南
  • 資料:
    • 1.開源網(wǎng)址中下載CF開頭的包谦炒,CF是CoreFoundation的縮寫置逻,CFRnLoop.c是實(shí)現(xiàn)文件
    • 2.官方文檔

Runloop與線程

  • 每條線程都有唯一的一個與之對應(yīng)的Runloop對象
    • CFRunLoopGet0(pthread_t t)
  • 主線程的Runloop已經(jīng)創(chuàng)建好了钩骇,子線程的Runloop需要主動創(chuàng)建
  • Runloop在第一次獲取時創(chuàng)建酪刀,在線程結(jié)束的時候銷毀

獲得Runloop對象

  • Foundation(OC)
    • [NSRunloop currentRunloop]
    • [NSRunloop mainRunloop]
  • Core Foundation(C)
    • CFRunLoopRef
    • CFRunLoopGetCurrent();
    • CFRunLoopGetMain();
  • 轉(zhuǎn)換OC-C
    • mainRunloop.getCFRunLoop
  • RunLoop和線程的關(guān)系 一一對應(yīng)
    • [NSThread detachNewThreadSelector:toTarget:withObject:]
    • 主線程對應(yīng)的RunLoop默認(rèn)已經(jīng)創(chuàng)建了眨猎,子線程對應(yīng)的runloop需要我們手動創(chuàng)建
  • [NSRunloop currentRunloop]是懶加載的卜高,只會初始化一次弥姻,會判斷當(dāng)前線程對應(yīng)的runloop是否存在南片,如果存在,直接返回庭敦,如果不存在會創(chuàng)建一個疼进,返回給我們,該方法就是用來創(chuàng)建Runloop的秧廉。

Runloop相關(guān)類

  • CoreFoundation中關(guān)于RunLoop的五個類:

    • CFRunLoopRef
    • CFRunLoopModeRef:運(yùn)行模式
    • CFRunLoopSourceRef:事件源伞广、輸入源
    • CFRunLoopTimerRef:定時器事件
    • CFRunLoopObserverRef:監(jiān)聽者、觀察者
  • 關(guān)系:

    • Runloop要啟動必須要選擇一種運(yùn)行模式疼电,有多種運(yùn)行模式可供選擇赔癌,但只能選擇一種
    • 運(yùn)行模式里面有source/observer/timer,runloop啟動之前會檢查運(yùn)行模式是否為空(怎么判斷是否為空澜沟?檢查有沒有source和timer,如果一個source和timer都沒有就為空灾票,如果為空,runloop啟動之后就退出了)
    • 開啟runloop運(yùn)行循環(huán)-死循環(huán)
  • 運(yùn)行模式:CFRunloopModeRef

    • CFRunLoopRef代表RunLoop的運(yùn)行模式
    • 一個RunLoop包含若干個Mode,每個Mode又包含若干個Source/Timer/Observer
    • 每一次Runloop啟動時茫虽,只能指定其中一個Mode,這個Mode被稱作currentMode
    • 如果需要切換Mode,只能退出Loop,再重新指定一個Mode進(jìn)入
    • 這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響

CFRunLoopModeRef

  • 系統(tǒng)默認(rèn)注冊了5個Mode
    • kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個Mode下運(yùn)行
    • UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView追蹤觸摸滑動刊苍,保證界面滑動時不受其他Mode影響
    • UIInitializationRunLoopMode:在剛啟動App時進(jìn)入第一個Mode,啟動完成后就不再使用
    • GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部Mode,通常用不到
    • KCFRunLoopCommonModes:這是一個占位用的Mode,不是一種真正的Mode

CFRunLoopModeRef和NSTimer

  • 定時器使用
    • 方法一:[NSTimer scheduledTimerWithTimerInterval:target:selector:userInfo:repeats:]
      • 該方法內(nèi)部會自動將創(chuàng)建的定時器對象添加到當(dāng)前的runloop當(dāng)中,并且指定runloop的運(yùn)行模式為默認(rèn)
      • 開一個子線程調(diào)用這個方法濒析,這個定時器能工作嗎正什?
        • 不能工作!:判印婴氮!子線程對應(yīng)的runloop沒有創(chuàng)建
        • 手動創(chuàng)建runloop,手動添加定時器,還是不可以6苤隆主经!
  • 注意
    • 子線程對應(yīng)的runloop需要手動的創(chuàng)建
    • 子線程創(chuàng)建的runloop還需要手動的開啟 [currentRunLoop run]
    • 方法二:[NSTimer timerWithTimerInterval:target:selector:userInfo:repeats:]
      • 使用這個方法需要把定時器添加到runloop中
      • [NSRunloop currentRunloop] addTimer:forMode:
      • 問題:拖拽textField的時候,定時器不工作的原因庭惜?
        • 滾動textField罩驻,runloop運(yùn)行模式改變了,切換到了UITrackingMode
      • 問題:怎么讓定時器护赊,在滾動textField的時候惠遏,也能工作?
        • 方法一:把定時器對象添加到runloop中,并且制定runloop的運(yùn)行模式為默認(rèn)骏啰,只有runloop的運(yùn)行模式為NSDefaultRunloopMode的時候定時器才工作节吮,要求在滾動textField的時候定時器也能工作,并且指定runloop的運(yùn)行模式為tracking的時候判耕,定時器才工作
        • 方法二:占位模式(等價于上面的兩行代碼)NSRunLoopCommonModes = Default & Tracking
          • NSRunLoopCommonModes表示把定時器對象添加到runloop中并且指定運(yùn)行模式為Default或者是Tracking

GCD中的定時器(掌握)

  • 第一個函數(shù):創(chuàng)建定時器對象
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE__TIMER,0,0,dispatchQueue)
- 第一個參數(shù):要創(chuàng)建的是一個定時器 
- 第二個參數(shù):默認(rèn)總是傳0透绩,描述信息
- 第三個參數(shù):默認(rèn)總是傳0,
- 第四個參數(shù):隊(duì)列,并發(fā)隊(duì)列(全局),決定代碼塊(dispatch_source_set_event_handler)在哪個線程中調(diào)用(主隊(duì)列+主線程)
  • 第二個函數(shù):設(shè)置定時器
dispatch_source_set_timer(timer,DISPATCH_TIME_NOW,intervalInSeconds *NSEC_PER_SEC,leewayInSeconds *NSEC_PER_SEC)
- 第一個參數(shù):定時器對象
- 第二個參數(shù):定時器開始計(jì)時的時間(開始時間)
    - 怎么修改開始時間渺贤?
        - dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW,延遲時間2.0*NSEC_PER_SEC)  
- 第三個參數(shù):設(shè)置間隔時間 GCD的時間單位:納秒
- 第四個參數(shù):精準(zhǔn)的雏胃,0表示絕對精準(zhǔn)
  • 第三個函數(shù):在block塊里面要執(zhí)行的任務(wù)请毛,GCD定時器時間到了之后要執(zhí)行的任務(wù)
dispatch_source_set_event_handler(timer,^{}) 
  • 第四個函數(shù):恢復(fù)執(zhí)行dispatch_resume(timer)

    • 默認(rèn)是暫停狀態(tài)志鞍,需要手動開啟
  • 定時器不工作的原因:

    • GCD是不會受到runloop的影響
    • 原因是timer被釋放了
      • 定一個屬性,讓timer不釋放

RunloopSourceRef

  • CFRunloopSourceRef:是事件源(輸入源)
  • 以前的分類:
    • Port-Based Source 基于端口
    • Custom Input Sources 自定義
    • Cocoa Perform Selector Sources
  • 現(xiàn)在的分類:
    • Source0:非基于Port
    • Source1:基于Port
    • 根據(jù)函數(shù)調(diào)用棧進(jìn)行分類的

RunloopObserverRef

  • 01.創(chuàng)建一個監(jiān)聽者
    • CFRunloopObserverRef observer = CFRunLoopObserverCreateWithHandler()

      • 第一個參數(shù)方仿,分配存儲空間
        • CFAllocatorGetDefault()
      • 第二個參數(shù):要監(jiān)聽的狀態(tài) 0
      • 第三個參數(shù):是否要持續(xù)監(jiān)聽YES
      • 第四個參數(shù):和優(yōu)先級相關(guān) 0
      • block塊:當(dāng)發(fā)現(xiàn)監(jiān)聽對象狀態(tài)改變的時候調(diào)用該block
        • KCFRunLoopEntry:runloop進(jìn)入啟動
        • BeforeTimers:runloop即將處理timer事件
        • BeforeSources: runloop即將處理source事件
        • beforeWaiting:runloop即將進(jìn)入睡眠狀態(tài)
        • afterWaiting: runloop被喚醒
        • exit:runloop即將退出
        • AllActivities:監(jiān)聽所有的活動狀態(tài)
    • 只能使用C語言代碼

  • 02.設(shè)置監(jiān)聽
    • CFRunLoopAddObserver()
      • CFRunloopRef,要監(jiān)聽的runloop對象
      • CFRunLoopObserverRef,監(jiān)聽者對象本身
      • CFStringRef:runloop的運(yùn)行模式
        • NSDefaultRunLoopMode(OC)
        • KCFRunLoopDefaultMode(C)??

runloop的運(yùn)行流程

  • 處理邏輯(網(wǎng)友整理)
    • 1.通知內(nèi)部Observer:即將進(jìn)入Loop
    • 2.通知Observer:將要處理Timer
    • 3.通知Observer:將要處理Source0
    • 4.處理Source0
    • 5.如果有Source1,跳到第九步
    • 6.通知Observer:線程即將休眠
    • 7.休眠等待喚醒
    • 8.通知Observer:線程剛被喚醒
    • 9.處理喚醒時收到的消息固棚,之后跳回2
    • 10.通知Observer:即將退出Runloop
  • 處理邏輯(官方)
    • Runloop的事件隊(duì)列
    • 每次運(yùn)行runloop,線程的runloop會自動處理之前未處理的消息仙蚜,并通知相關(guān)的觀察者此洲,具體的順序如下:
      • 1.通知觀察者runloop已經(jīng)啟動
      • 2.通知觀察者任何即將要開始的定時器
      • 3.通知觀察者任何即將啟動的非基于端口的源
      • 4.啟動任何準(zhǔn)備好的的非基于端口的源
      • 5.如果基于端口的源準(zhǔn)備好并處于等待狀態(tài),立即啟動委粉,并進(jìn)入步驟9
      • 6.通知觀察者線程進(jìn)入休眠
      • 7.將線程置于休眠直到下面的任意一個事件發(fā)生
        • 某一時間到達(dá)基于端口的源
        • 定時器啟動
        • Runloop設(shè)置的時間已經(jīng)超時
        • runloop被顯示喚醒
      • 8.通知觀察者線程將被喚醒
      • 9.處理未處理的事件
        • 如果用戶定義的定時器啟動呜师,處理定時器事件并重啟runloop,進(jìn)入步驟2
        • 如果輸入源啟動,傳遞相應(yīng)的消息
        • 如果runloop被顯示喚醒而且時間還沒超時贾节,重啟runloop,進(jìn)入步驟2
      • 10.通知觀察者runloop結(jié)束

代碼模擬runloop死循環(huán)


void msg(int n)
{
    NSLog(@"runloop被喚醒");
    NSLog(@"runloop處理%zd事件",n);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
       
        NSLog(@"runloop啟動了");
        do {
            
            NSLog(@"runloop即將處理timer事件");
            NSLog(@"runloop即將處理source0事件");
            NSLog(@"source1事件");
            NSLog(@"runloop詢問:還有事件需要我處理嗎?");
            NSLog(@"runloop計(jì)入到休眠狀態(tài)");
            
            int number = 0;
            scanf("%zd",&number);
            msg(number);
            
            
        } while (1);
    }
    return 0;
}

runloop的應(yīng)用

  • NSTimer
  • ImageView顯示
  • PerformSelector
  • selecter事件和runloop的關(guān)系
    • performSelector:withObject:afterDelay:inModes:
      • @[NSDefaultRunLoopMode,UITrackingRunLoopMode]
      • @[NSRunLoopCommonModes]
    • 默認(rèn)情況下汁汗,selector事件是被添加到當(dāng)前的runloop中執(zhí)行的,并且指定了運(yùn)行模式為默認(rèn)
-(void)test1
{
     [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];
}

-(void)task
{
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20160713_9"] afterDelay:3.0];
    //運(yùn)行模式啟動之后栗涂,判斷有一個selector事件知牌,runloop才能夠開啟
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop run];
    
    NSLog(@"+++++");
}
//需要進(jìn)行線程間通信,否則會報錯
  • 常駐線程
    • 開一條子線程斤程,讓子線程一直工作
    • 獲得當(dāng)前線程對應(yīng)的runloop對象
    • 為runloop添加input source 或者是timer source
    • 啟動runloop
//創(chuàng)建線程角寸,執(zhí)行任務(wù)
- (IBAction)createNewThreadBtnClick:(id)sender {
    
    //01 創(chuàng)建線程,執(zhí)行任務(wù)
    //因?yàn)橐玫骄€程對象,所以用NSThread創(chuàng)建線程
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run1) object:nil];
    
    //02 執(zhí)行任務(wù)
    [thread start];
    
    self.thread = thread;
}


//繼續(xù)執(zhí)行任務(wù)
- (IBAction)goOnBtnClick:(id)sender {
    
    /*
    self.thread 任務(wù)執(zhí)行完畢,已經(jīng)進(jìn)入到死亡狀態(tài)|但是還沒有被釋放
    程序崩潰報錯:attempt to start the thread again 
    [self.thread start];?不能嘗再次開啟線程
    怎么讓任務(wù)執(zhí)行不完呢忿墅?在run1方法里搞一個死循環(huán)扁藕?這樣做線程不會死,但是不會執(zhí)行任務(wù)2
    正確做法:在run1方法里面開啟一個runloop
    */
    
    //讓之前創(chuàng)建的線程繼續(xù)執(zhí)行
    [self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:YES];
    
}

-(void)run1
{
    NSLog(@"run1---%@",[NSThread currentThread]);
    
    /*
    do {
        NSLog(@"-%@",[NSThread currentThread]);
    } while (1);
     */
    
    //001 獲得當(dāng)前線程對應(yīng)的runloop對象
    NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
    
    //002 為runloop添加input soucre或者是timer souce ,為了讓運(yùn)行模式不為空疚脐,否則不能開啟runloop
    //基于端口的事件
    //好處:不需要調(diào)用方法纹磺,開發(fā)中常用
    [currentRunloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    
    //Perform事件
    //存在的問題:3.0s之后就退出了,開發(fā)中一般不用selector方法
    //[self performSelector:@selector(run3) withObject:nil afterDelay:3.0];
    //NSTimer事件
    //[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
    
    //003 啟動runloop
    //runUntilDate |run 內(nèi)部都指定了運(yùn)行模式為默認(rèn)
    [currentRunloop run];
    //[currentRunloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10000000]];
    
    NSLog(@"_______end____");//驗(yàn)證runloop是否開啟了,打印(沒有開啟runloop)
    
}

-(void)run2
{
    NSLog(@"run2---%@",[NSThread currentThread]);
}
-(void)run3
{
    NSLog(@"run3---%@",[NSThread currentThread]);
}

-(void)timerTest
{
    NSLog(@"timer---");
}


runloop常見面試題

  • 什么是runloop?

    • 從字面意思看:運(yùn)行循環(huán)亮曹、跑圈
    • 其實(shí)它內(nèi)部就是do while循環(huán)橄杨,在這個循環(huán)內(nèi)部不斷地處理各種任務(wù)(比如Source/Timer/Observer)
    • 一個線程對應(yīng)一個Runloop,主線程的runloop默認(rèn)已經(jīng)啟動照卦,子線程的runloop需要手動啟動(調(diào)用run方法)
    • Runloop只能選擇一個mode啟動式矫,如果當(dāng)前mode中沒有任何source(source0/source1)/timer,那么就直接退出runloop
  • 自動釋放池什么時候釋放役耕?

    • 通過observer監(jiān)聽runloop的狀態(tài)
    • 自動釋放池的創(chuàng)建和釋放
      • 創(chuàng)建:runloop啟動的時候
      • 銷毀:runloop退出的時候
      • 其他時候的創(chuàng)建和銷毀:當(dāng)runloop即將進(jìn)入到休眠的時候會把之前的釋放池銷毀,重新創(chuàng)建一個新的自動釋放池
  • 在開發(fā)中如何使用runloop?應(yīng)用場景采转?

    • 開啟一個常駐線程(讓一個子線程不進(jìn)入消亡狀態(tài),等待其他線程發(fā)來消息,處理其他事件)
      • 在子線程中開啟一個定時器
      • 在子線程中進(jìn)行一些長期監(jiān)控
    • 可以控制定時器在特定模式下執(zhí)行
    • 可以讓某些事件(行為故慈、任務(wù))在特定模式下執(zhí)行
    • 可以添加Observer監(jiān)聽Runloop的狀態(tài)板熊,比如監(jiān)聽點(diǎn)擊事件的處理(在所有點(diǎn)擊事件之前做一些事情)

總結(jié):

  • 多圖下載

    • 緩存策略:內(nèi)存緩存+磁盤緩存
    • 開子線程下載
  • SDWebImage

    • 基本使用
    • 結(jié)構(gòu)
      • caches
      • download
      • manager
    • 細(xì)節(jié)
  • Runloop

    • 概念:運(yùn)行循環(huán)(死循環(huán))
    • 作用【3】
      • 保持程序的持續(xù)運(yùn)行
      • 處理app中的各種事件
      • 提高性能
    • 與線程關(guān)系:一一對應(yīng)(字典)
      • 線程與runloop是一一對應(yīng)的關(guān)系(字典)
      • 主線程對應(yīng)的runloop已經(jīng)創(chuàng)建并且啟動mainRunloop
      • 子線程對應(yīng)的runloop需要手動創(chuàng)建并且開啟
      • 退出:線程銷毀
    • runloop的相關(guān)類
      • runloopRef runloop本身
      • runloopModeRef 運(yùn)行模式[5]
        • 默認(rèn)Default
        • 界面追蹤Tracking
        • commonModes
      • runloopTimeRef
      • observer 監(jiān)聽者
      • sourceRef 事件源
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市察绷,隨后出現(xiàn)的幾起案子干签,更是在濱河造成了極大的恐慌,老刑警劉巖拆撼,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件容劳,死亡現(xiàn)場離奇詭異,居然都是意外死亡闸度,警方通過查閱死者的電腦和手機(jī)竭贩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來莺禁,“玉大人留量,你說我怎么就攤上這事∮炊” “怎么了楼熄?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長柒傻。 經(jīng)常有香客問我孝赫,道長,這世上最難降的妖魔是什么红符? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任青柄,我火速辦了婚禮,結(jié)果婚禮上预侯,老公的妹妹穿的比我還像新娘致开。我一直安慰自己,他們只是感情好萎馅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布双戳。 她就那樣靜靜地躺著,像睡著了一般糜芳。 火紅的嫁衣襯著肌膚如雪飒货。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天峭竣,我揣著相機(jī)與錄音塘辅,去河邊找鬼。 笑死皆撩,一個胖子當(dāng)著我的面吹牛扣墩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼呻惕,長吁一口氣:“原來是場噩夢啊……” “哼荆责!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起亚脆,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤做院,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后型酥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體山憨,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡查乒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年弥喉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玛迄。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡由境,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蓖议,到底是詐尸還是另有隱情虏杰,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布勒虾,位于F島的核電站纺阔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏修然。R本人自食惡果不足惜笛钝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望愕宋。 院中可真熱鬧玻靡,春花似錦、人聲如沸中贝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邻寿。三九已至蝎土,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绣否,已是汗流浹背誊涯。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枝秤,地道東北人醋拧。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親丹壕。 傳聞我的和親對象是個殘疾皇子庆械,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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