RunLoop

RunLoop的基本了解

  • **1 . RunLoop字面的意思 : **運行循環 / 跑圈

  • **2 . 基本作用 : **
    1.保證程序的持續運行(這就是為什么ios程序能一直活著不會死)
    2.處理app中的各種事件(比如觸摸事件、定時器事件(NSTimer)、selector事件(performSelector)
    3.節省CPU資源,提高程序性能,有事情就做事情,沒事情就休息

  • 3 . 重要說明
    (1)如果沒有Runloop,那么程序一啟動就會退出,什么事情都做不了;如下圖,當程序執行完第2行,在執行到低3行后就結束程序.

    沒有RunLoop的程序.png

    (2)如果有了Runloop,那么相當于在內部有一個死循環,能夠保證程序的持續運行;如下圖,main函數內有了一個RunLoop(由do-while循環模擬),程序會持續的運行,不會馬上就退出.


    有RunLoop的程序.png

    (3)main函數中的Runloop


    main函數中的RunLoop.png

    a. 在UIApplication函數內部就啟動了一個Runloop,所以UIApplicationMain函數一直沒有返回,保持了程序的還需運行,該函數返回一個int類型的值
    b. 這個默認啟動的Runloop是跟主線程相關聯的

  • RunLoop與線程

    • 1.Runloop和線程的關系:每條線程,都有唯一的一個RunLoop對象與之相對應
      問題:如何讓子線程不死
      回答:給這條子線程開啟一個Runloop
    • 2.Runloop的創建:
      1.主線程Runloop已經創建好了
      2.子線程的runloop需要手動創建
    • 3.Runloop的生命周期:在第一次獲取時創建,在線程結束時銷毀
  • RunLoop對象
    1.在iOS開發中有兩套api來訪問Runloop
    a.Foundation 框架 以NS開頭 - NSRunloop
    b.Core Foundation 框架 以CF開頭 - CFRunloopRef
    2.NSRunLoop和CFRunLoopRef都代表著RunLoop對象,它們是等價的,可以互相轉換
    3.NSRunLoop是基于CFRunLoopRef的一層OC包裝,更加的面向對象,所以要了解RunLoop內部結構,需要多研究CFRunLoopRef層面的API(Core Foundation層面)

  • 獲取RubLoop對象

    • 獲取Foundation框架下的RunLoop對象
/*****通過Foundation框架獲取*****/
    //獲取當前runLoop
    NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
    //獲取主runloop
    NSRunLoop * mainRunLoop = [NSRunLoop mainRunLoop];
    NSLog(@"%p-------%p",runLoop,mainRunLoop);
  • 獲取Core Foundation框架下的RunLoop對象
//*****通過Core Foundation框架獲取*****//
    //獲取當前的runloop
    CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
    //獲取主runLoop
    CFRunLoopRef cfMainRunLoop = CFRunLoopGetMain();
    NSLog(@"%p-------%p",cfRunLoop,cfMainRunLoop);
  • Foundation框架的RunLoop對象轉換成Core Foundation框架的RunLoop對象
    **注意 : **雖然說Foundation框架下的和Core Foundation框架下的RunLoop對象是相同的,但是只有通過轉換后的地址才是相同的
CFRunLoopRef changeRunLoop = mainRunLoop.getCFRunLoop;
  • RunLoop與子線程之間的關系
1)子線程與RunLoop之間是一一對應的
2)主線程對應的runloop默認已經開啟了,子線程對應的runloop需要主動創建
3)如何創建子線程對應的runloop :[NSRunLoop currentRunLoop](該方法本身是懶加載的,第一次調用該方法的時候如果發現runloop不存在那么會直接創建一個)
4)runloop是需要開啟
/***runLoop和線程之間的關系***/
//創建一個子線程
[NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];
/
-(void)task
{
    NSLog(@"執行了task");
    //下一行代碼內,已經創建了子線程對應的runLoop,但是子線程的RunLoop需要手動開啟,開啟的方法是通過子線程的RunLoop調用run方法
    NSLog(@"%p---------%p",[NSRunLoop currentRunLoop],[NSRunLoop mainRunLoop]);
    //雖然已經開啟了子線程的RunLoop,但是此處子線程的RunLoop并沒有運行起來,因為在RunLoop內,至少要有一個源或者Timer,這樣runLoop才不會在剛創建就結束運行
    [[NSRunLoop currentRunLoop] run];
    
    NSLog(@"------------%@",[NSThread currentThread]);
}

如何持續運行子線程內的RunLoop:
1.子線程內的RunLoop需要手動開啟,通過子線程對象,調用run方法"[[NSRunLoop currentRunLoop] run];"
2.在子線程內的RunLoop內,至少要有一個源(事件源)或者Timer,這樣才能包裝RunLoop不會再剛創建就結束運行
**注意點 : **
1.開一個子線程創建runloop,不是通過alloc init方法創建,而是直接通過調用currentRunLoop方法來創建,它本身是一個懶加載的。
2.在子線程中,如果不主動獲取Runloop的話,那么子線程內部是不會創建Runloop的。可以下載CFRunloopRef的源碼,搜索_CFRunloopGet0,查看代碼。
3.Runloop對象是利用字典來進行存儲,而且key是對應的線程,Value為該線程對應的Runloop。

RunLoop的相關類

  • RunLoop一共有五個與之相關的類
    a.CFRunloopRef
    b.CFRunloopModeRef【Runloop的運行模式】
    c.CFRunloopSourceRef【Runloop要處理的事件源】
    d.CFRunloopTimerRef【Timer事件】
    e.CFRunloopObserverRef【Runloop的觀察者(監聽者)】
    • CFRunloopModeRef - RunLoop的運行模式
      Runloop要想跑起來:
      1.它的內部必須要有一個mode,
      2.這個mode里面必須有source\timer(事件),至少要有其中的一個(事件),如果僅僅是有observer,那么RunLoop也會立即退出。
      01.CFRunloopModeRef代表著Runloop的運行模式
      02.一個Runloop中可以有多個mode,一個mode里面又可以有多個source\observer\timer等等
      03.每次runloop啟動的時候,只能指定一個mode,這個mode被稱為該Runloop的當前mode
      04.如果需要切換mode,只能先退出當前Runloop,再重新指定一個mode進入
      05.這樣做主要是為了分割不同組的定時器等,讓他們相互之間不受影響
      06.系統默認注冊了5個mode
      a.kCFRunLoopDefaultMode(C語言寫法):App的默認Mode,通常主線程是在這個Mode下運行
      NSDefaultRunLoopMode (OC語言寫法)
      b.UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
      c.UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
      d.GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到
      e.kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode,不過這個Model一般等同于同時擁有默認的Model和界面跟蹤Model
      即kCFRunLoopCommonModes = kCFRunLoopDefaultMode + UITrackingRunLoopMode
    • RunLoop與相關類之間的關系
RunLoop與相關類之間的關系.png
  • CFRunloopTimerRef
    RunLoop中的定時器,基本等同于OC中的定時器,在RunLoop中的定時器,一般指的是NSTimer定時器GCD定時器兩種.
    • NSTimer定時器
      說明:
      1. runloop一啟動就會選中一種模式,當選中了一種模式之后其它的模式就都不鳥。
      2. 一個mode里面可以添加多個NSTimer,也就是說以后當創建NSTimer的時候,可以指定它是在什么模式下運行的。
      3. 它是基于時間的觸發器,說直白點那就是時間到了我就觸發一個事件,觸發一個操作?;旧险f的就是NSTimer
      創建NSTimer定時器的方法
      注意 : 如果RunLoop的運行模式改變,那么在當前模式下的定時器就會停止工作

//方法一
scheduledTimerWithTimeInterval:
方法的說明 : 創建定時器并默認添加到當前線程的Runloop中,
并指定運行模式為默認的運行模式(kCFRunLoopDefaultMode)
示例:
-(void)timer2
{
//NSTimer 調用了scheduledTimer方法,那么會自動添加到當前的runloop里面去,而且runloop的運行模式kCFRunLoopDefaultMode
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
}
/**************愉快的分割線***************/
//方法二
timerWithTimeInterval:
方法說明 : 通過這個方法創建的定時器,
如果該定時器要工作還需要添加到runloop中,
并需要明確指定相應的運行模式
-(void)timer1
{
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//定時器添加到UITrackingRunLoopMode模式,一旦runloop切換模式,那么定時器就不工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//定時器添加到NSDefaultRunLoopMode模式,一旦runloop切換模式,那么定時器就不工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//占位模式:common modes標記
//被標記為common modes的模式 等同于同時可以在kCFRunLoopDefaultMode + UITrackingRunLoopMode下運行
//在這個模式下,RunLoop既可以運行在Default模式下,也可以運行在Tracking模式下,
//在這兩種模式來回切換時,定時器都不會停止工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

    - **GCD定時器**
    **GCD定時器的特點 : **GCD的定時器不會受到Runloop運行模式的影響,可以控制任務在主線程還是子線程執行
    **注意點 : **
          1.  需要添加強引用,否則在創建GCD定時器的代碼塊結束后,定時器就會被釋放掉
          2.  注意隊列里面不要添加了其它的任務
**創建GCD定時器方法介紹**
  1.獲取隊列(不再贅述)
2.創建一個GCD定時器

參數解釋:
第一個參數:DISPATCH_SOURCE_TYPE_TIMER 定時器
第二個參數:描述信息
第三個參數:總是穿0
第四個參數:隊列 (決定GCD要執行的任務在哪個線程中調用的 并發隊列--子線程中調用)
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

3.設置定時器的開始時間,間隔時間以及精確度

//設置開始時間
參數解釋:
第一個參數:從什么時間開始
第二個參數:多長時間后開始調用
dispatch_time_t start =dispatch_time(DISPATCH_TIME_NOW,3.0 *NSEC_PER_SEC);
//設置間隔時間
uint64_t intevel = 1.0 * NSEC_PER_SEC;
//設置定時器以及精確度
參數解釋:
第一個參數:定時器對象
第二個參數:從什么時候開始計時 DISPATCH_TIME_NOW 現在
第三個參數:間隔時間 2.0 ns
第四個參數:精準度 允許的誤差 絕對精準~0
dispatch_source_set_timer(timer, start, intevel, 0 * NSEC_PER_SEC);

4.設置定時器開始后回調的方法

dispatch_source_set_event_handler(timer, ^{
要執行回調的代碼
});

5.執行定時器

dispatch_resume(timer);

**創建GCD定時器 示例**
  //0.創建一個隊列

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

  //1.創建一個GCD的定時器

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

  //2.設置定時器的開始時間,間隔時間以及精準度

dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW,3.0 *NSEC_PER_SEC);
uint64_t intevel = 1.0 * NSEC_PER_SEC;
dispatch_source_set_timer(timer, start, intevel, 0 * NSEC_PER_SEC);

  //3.設置定時器開啟后回調的方法

dispatch_source_set_event_handler(timer, ^{
NSLog(@"------%@",[NSThread currentThread]);
});

  //4.執行定時器
        dispatch_resume(timer);

  //強引用
        self.timer = timer;
**GCD定時器補充 : **
 DISPATCH_SOURCE_TYPE_TIMER         定時響應(定時器事件)
 DISPATCH_SOURCE_TYPE_SIGNAL        接收到UNIX信號時響應

 DISPATCH_SOURCE_TYPE_READ          IO操作,如對文件的操作、socket操作的讀響應
 DISPATCH_SOURCE_TYPE_WRITE         IO操作,如對文件的操作、socket操作的寫響應
 DISPATCH_SOURCE_TYPE_VNODE         文件狀態監聽,文件被刪除、移動、重命名
 DISPATCH_SOURCE_TYPE_PROC          進程監聽,如進程的退出、創建一個或更多的子線程、進程收到UNIX信號

 下面兩個都屬于Mach相關事件響應
 DISPATCH_SOURCE_TYPE_MACH_SEND
 DISPATCH_SOURCE_TYPE_MACH_RECV
 下面兩個都屬于自定義的事件,并且也是有自己來觸發
 DISPATCH_SOURCE_TYPE_DATA_ADD
 DISPATCH_SOURCE_TYPE_DATA_OR

  - **CFRunloopSourceRef**
(1)是事件源也就是輸入源,有兩種分類模式;
              a.一種是按照蘋果官方文檔進行劃分的
              b.另一種是基于函數的調用棧來進行劃分的(source0和source1)。
(2)具體的分類情況
            a.以前的分法
                Port-Based Sources
                Custom Input Sources
                Cocoa Perform Selector Sources
            b.現在的分法
                Source0:非基于Port的(用戶主動觸發的事件)
                Source1:基于Port的
(3)可以通過打斷點的方式查看一個方法的函數調用棧

  - **CFRunLoopObserverRef**
      - **作用 :** 能夠監聽RunLoop的狀態改變
      - **監聽的狀態 : **
            typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
                kCFRunLoopEntry = (1UL << 0),   //即將進入Runloop
                kCFRunLoopBeforeTimers = (1UL << 1),    //即將處理NSTimer
                kCFRunLoopBeforeSources = (1UL << 2),   //即將處理Sources
                kCFRunLoopBeforeWaiting = (1UL << 5),   //即將進入休眠
                kCFRunLoopAfterWaiting = (1UL << 6),    //剛從休眠中喚醒
                kCFRunLoopExit = (1UL << 7),            //即將退出runloop
                kCFRunLoopAllActivities = 0x0FFFFFFFU   //所有狀態改變
            };
      - **如何監聽**
             //創建一個runloop監聽者
                CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
              //通過switch判斷activity的狀態
                      switch (activity) {
                      case kCFRunLoopEntry: NSLog(@"進入");
                              break;
                          case kCFRunLoopBeforeTimers:
                      NSLog(@"即將處理timer事件");
                              break;
                          case kCFRunLoopBeforeSources:
                      NSLog(@"將處理soure事件");
                              break;
                          case kCFRunLoopBeforeWaiting:
                      NSLog(@"即將進入休眠");
                              break;
                          case kCFRunLoopAfterWaiting:
                      NSLog(@"被喚醒");
                                  break;
                              case kCFRunLoopExit:
                      NSLog(@"runloop退出");
                                  break;
                              default:
                      break; }
                });
                //為runloop添加一個監聽者
                CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

                CFRelease(observer);

#Runloop運行邏輯
每次運行RunLoop,線程中的RunLoop都會自動處理之前未處理的消息,并通知相關的觀察者.具體的順序如下:
1. 通知觀察者,RunLoop已經啟動
2.通知觀察者任何即將要開始的定時器
3.通知觀察者任何即將要啟動的非基于端口的源
4.啟動任何準備好的非基于端口的源
5.如果基于端口的源準備好,并處于等待狀態,立即啟動;并進入步驟9
6.通知觀察者線程進入休眠
7.將下城置于休眠,知道下面任意事件的發生:
      -某一事件到達基于端口的源
      -定時器啟動
      -RunLoop設置的時間已經超時
      -RunLoop被顯示喚醒
8. 通知觀察者線程將被喚醒
9. 處理未處理的事件
      -如果用戶定義的定時器啟動,處理定時器時間,并重啟RunLoop.進入步驟2
      -如果輸入源啟動,傳遞相應的消息
      -如果RunLoop被顯示喚醒,而且時間還沒到,重啟RunLoop.進入步驟2
10.通知觀察者RunLoop結束
![RunLoop的運行邏輯.png](http://upload-images.jianshu.io/upload_images/2292909-0df986c318a53448.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#Runloop應用
    1)NSTimer
    2)ImageView顯示:控制方法在特定的模式下可用
    3)PerformSelector
    4)常駐線程:在子線程中開啟一個runloop
           子線程的運行循環需要手動創建
           子線程的運行循環開啟后如果內部什么都沒有那么會直接退出
           添加Port或添加NSTimer, 只是添加Observer無效
           子線程的運行循環需要手動開啟
    5)自動釋放池
        第一次創建:進入runloop的時候
        最后一次釋放:runloop退出的時候
        其它創建和釋放:當runloop即將休眠的時候會把之前的自動釋放池釋放,然后重新創建一個新的釋放池
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容