<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                [toc] ## RunLoop是什么?有什么作用? `RunLoop`:翻譯過來是運行環路(中式翻譯: 跑圈)。我們在創建命令行項目和創建ios項目時,發現命令行項目當最后一行代碼執行完后項目就自動退出了,而ios項目確可以一直運行,知道用戶手動點擊退出按鈕。這就是因為ios項目在main函數中自動創建了runLoop,從而可以使項目可以一直響應用戶的操作。 ``` int main(int argc, char * argv[]) { @autoreleasepool { //這行代碼 會自動創建主線程的RunLoop return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ``` 我們可以將這個過程我們可以簡化成: ![](https://img.kancloud.cn/3a/ab/3aab8e377e133f62cb08fcdf96c468c9_891x487.png) 我們從這個過程可以看出RunLoop的基本作用: - 保持程序的持續運行 - 處理App中的各種事件(比如觸摸事件、定時器事件等) - 節省CPU資源,提高程序性能:該做事時做事,該休息時休息 - ...... 我們平時開發中,涉及到RunLoop的挺多的,比如說定時器、手勢識別、網絡請求等等. ![](https://img.kancloud.cn/78/df/78dfa2947f327a8ae0ba4280dfdedbfa_750x500.png) ## 一、RunLoop的結構 iOS中有2套API來訪問和使用RunLoop: >① Foundation:`NSRunLoop`,它是基于 CFRunLoopRef 的封裝,提供了面向對象的 API,但是這些 API `不是線程安全`的。 >② Core Foundation:`CFRunLoopRef`,它提供了純 C 函數的 API,所有這些 API都是`線程安全`的。([CFRunLoopRef](https://opensource.apple.com/tarballs/CF/)是開源的) 兩者關系: ![](https://img.kancloud.cn/b3/ae/b3ae06021348f6257baa614e28ae11b5_592x416.png) 所以我們獲取RunLoop對象也有兩種方法: ``` // Foundation [NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象 [NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象 // Core Foundation CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象 CFRunLoopGetMain(); // 獲得主線程的RunLoop對象 ``` 因為CFRunLoopRef是開源的,所以我們可以通過它來看一下它的實現結構。來到CFRunLoop.c文件中,找到了RunLoop的結構體定義: ``` //已剔除非必要部分struct __CFRunLoop { pthread_t _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; }; ``` >這里的Set和數組類似,只不過數組是有序的,而set是無序的,都是用來存放數據的,所以 CFMutableSetRef可以理解成可變數組,也就是說在一個RunLoop對象中,存儲著一個線程對象,三個可變數組,一個當前模式。 **那么`CFRunLoopModeRef`又是什么呢**?我們找到了它的定義:   ``` typedef struct __CFRunLoopMode *CFRunLoopModeRef; //剔除了其他無關屬性 struct __CFRunLoopMode { CFStringRef _name; CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; }; ``` **所以RunLoop的結構是這樣的:** ![](https://img.kancloud.cn/fc/9b/fc9b12191aa06c5bb986f318dcb1fe0e_1676x658.png) - `_pthread`就是RunLoop對應的`線程`,每條線程都有唯一的一個與之對應的RunLoop對象。 - `_commonModeItems`和`_commonModes`是用來存放某些特定模式和模式內事件的,接下來會講到。 - `_currentMode`,RunLoop當前所處的模式,當前模式是從`_modes`里面選擇的。 - `_modes`:RunLoop的運行模式,一共有`五種`,但是我們經常用的就兩三種: >- `kCFRunLoopDefaultMode`: App的默認運行模式,通常`主線程`是在這個運行模式下運行 >- `UITrackingRunLoopMode`: `跟蹤用戶交互事件`(用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他Mode影響)頁面滾動式所處的模式 >- `kCFRunLoopCommonModes`: `偽模式`,不是一種真正的運行模式 >- UIInitializationRunLoopMode:在剛啟動App時第進入的第一個Mode,啟動完成后就不再使用 >- GSEventReceiveRunLoopMode:接受系統內部事件,通常用不到 我們上面提到的`_commonModeItems`和`_commonModes`就是存放`kCFRunLoopCommonModes`這種模式的數據的。CommonModes其實并不是一種真正的模式,而是指可以在標記為`Common Modes`的模式下運行的`偽模式`。 簡單來說目前kCFRunLoopCommonModes就是指: `kCFRunLoopDefaultMode+UITrackingRunLoopMode`。比如,我們經常遇到在tableview添加定時器后,當tableview滾動后timer就不響應了。 >這是因為tableview滾動式處在`UITrackingRunLoopMode`模式下的,而定時器默認是處在`kCFRunLoopDefaultMode`下的,所以當模式切換后,RunLoop就無法響應之前模式的時間了,故而無法響應定時器時間。所以我們的方案是將定時器添加到RunLoop的`kCFRunLoopCommonModes`模式下,這樣無論是否滑動tableview都可以響應定時器事件了。 >這里還需要注意的一點是:如果需要`切換 Mode`,只能`退出Loop`,再重新指定一個 Mode 進入。這樣做主要是為了`分隔`開不同組的 `Source/Timer/Observer`,讓其互不影響。 **接下來,我們再來看一下這個RunLoop中的模式指的是什么?有什么作用?** 我們前面通過源碼,看到了`CFRunLoopMode`的結構,里面有`sources0、sources1、timer、observers`,其實這里面就存儲著app要處理的種種事情,它們分別負責不同的工作。它們的分工是這樣的:(個人認為sources0和sources1其實是一個整體,當事件發生時sources1先去獲取這個時間,涉及不到端口或內核或其他線程的事情的話就交給sources0處理,其余的自己處理) - `sources0`:只包含了一個`回調`(函數指針),它并不能主動觸發事件,比如點擊事件等操作都是通過sources0處理的。 - `sources1`:包含了一個 `mach_port` 和一個`回調`(函數指針),用于通過內核和其他線程相互發送消息,這種 Source 能`主動喚醒` RunLoop 的線程。 - `timer`:是基于`時間`的`觸發器`,其包含一個`時間長度`和一個`回調`(函數指針)。當其加入到 RunLoop 時,RunLoop會`注冊`對應的時間點,當時間點到時,RunLoop會被`喚醒`以`執行`那個回調。 - `observers`:是`觀察者`,當 RunLoop 的`狀態`發生變化時,觀察者就能通過回調接收到這個變化。 RunLoop的狀態有一下幾種: ![](https://img.kancloud.cn/fc/74/fc746e215c2dda7797e1f734e5930c48_1262x517.png) **總結 `CFRunLoopMode`的圖示:** ![](https://img.kancloud.cn/3b/70/3b7019d4b94582d8dd112466c5f6c59d_954x824.png) >需要注意的一點是:如果Mode里沒有任何`Source0/Source1/Timer/Observer`,RunLoop會`立馬退出`。 ## 二、RunLoop與線程 關于RunLoop與線程的關系,我們可以總結以下幾點: >1. 每條線程都有`唯一`的一個`與之對應`的RunLoop對象 (也就是RunLoop宿主于線程) >2. 線程剛創建時并沒有RunLoop對象,`RunLoop會在第一次獲取它時創建` >3. `主線程的RunLoop已經自動獲取(創建)`,`子線程默認沒有開啟RunLoop`,子線程沒有開啟RunLoop的話就跟命令行項目一樣,任務執行完就會結束 >4. RunLoop保存在一個`全局的Dictionary里`,`線程作為key`,`RunLoop作為value` >5. RunLoop會在線程結束時`銷毀 ` 接下來,我們通過源碼來驗證: 當我們獲取線程的Runloop的時候,發現RunLoop沒有獲取到話,都會調用`__CFRunLoopGet0`, 并把線程作為參數傳遞 ![](https://img.kancloud.cn/be/88/be88039a30e46af90e6a99e73f0cc259_1080x434.png) 繼續,跳轉至__CFRunLoopGet0,如下: ![](https://img.kancloud.cn/ec/bf/ecbff424b5bcab4a6a25e8976cf69815_1080x792.png) >發現,RunLoop與線程的關系是`一對一`的,并且用了個`全局字典`保存了起來,`線程作為key`,`RunLoop作為value`。 我們發現如果線程沒有啟用RunLoop后會執行完馬上銷毀: ![](https://img.kancloud.cn/ee/1a/ee1a0718a00d4fd3293610eb0a8fd164_1334x600.png) >添加RunLoop后,發現還是運行完就銷毀:這是因為如果`Mode里`沒有任何`Source0/Source1/Timer/Observer`,RunLoop會立馬退出 ![](https://img.kancloud.cn/f9/f3/f9f362694ce948e4f112b0c62377d917_1286x816.png) 所以我們需要往`Model中`添加一個數據: ![](https://img.kancloud.cn/eb/cf/ebcf0778e7820a1872af3d064e0f4dcf_1600x894.png) 發現確實執行完后,線程阻塞了,一直沒有被銷毀,這是因為當runloop創建后,如果沒有被事件喚醒后它就一直在`休眠`,cpu就不會繼續處理事情,所以`阻塞`在這。 ## 三、RunLoop的運行邏輯  我們在了解RunLoop的結構以及與線程的關系后,我們再來看一下RunLoop的運行流程: ![](https://img.kancloud.cn/69/d8/69d8ad58baa25db1b0b3430227b8c9ad_1345x703.png) **接下來,我們通過源碼來看一下RunLoop是如何處理這些事件的?** 關于入口的查找,我們可以現在touchesBegan:方法中打個斷點,查看程序是怎么執行到這的: ![](https://img.kancloud.cn/84/12/8412d35a6fcdc27906af370074e0e0d6_1778x876.png) ``` //RunLoop入口 SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ //通知Observers 進入RunLoop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); //RunLoop的具體運行 result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); //通知Observers 退出RunLoop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); return result; } //RunLoop的具體運行 static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { int32_t retVal = 0; do { //通知Observers 即將處理Timers __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); //通知Observers 即將處理Sources __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); //處理Block __CFRunLoopDoBlocks(rl, rlm); //處理Sources0 if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) { //處理Block __CFRunLoopDoBlocks(rl, rlm); } Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); // 如果當前是主線程的runloop,并且主線程有事情需要處理,則跳轉至handle_msg處理,即跳過休眠 這條指令網上大部分說法是指判斷Sources1中是否有事情處理,個人覺得這個說法不太對,這篇文章中有正面:資料 if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { goto handle_msg; } //通知Observers 即將休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); //開始休眠 __CFRunLoopSetSleeping(rl); //等待別的消息來喚醒當前線程 如果沒有消息就會一直在這休眠 阻塞在這 cpu不工作 有消息的話則喚醒執行 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); //結束休眠 __CFRunLoopUnsetSleeping(rl); //通知Observers 結束休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); //handle_msg handle_msg:; if (被timer喚醒) { //處理Timers __CFRunLoopDoTimers(rl, rlm, mach_absolute_time()) } else if (被gcd喚醒) { //處理gcd __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); } else {//被sources1喚醒 //處理Sources1 __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) } //處理Block __CFRunLoopDoBlocks(rl, rlm); //處理返回值 if (sourceHandledThisLoop && stopAfterHandle) { retVal = kCFRunLoopRunHandledSource; } else if (timeout_context->termTSR < mach_absolute_time()) { retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(rl)) { __CFRunLoopUnsetStopped(rl); retVal = kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { retVal = kCFRunLoopRunFinished; } } while (0 == retVal); return retVal; } ``` 簡化成流程圖 則是: ![](https://img.kancloud.cn/14/4a/144a09f3c28d6edd4bfc29792db981bf_1323x679.png) ## 四、RunLoop的應用 - [控制線程生命周期(線程保活)](https://www.cnblogs.com/chglog/p/9585068.html) - [解決NSTimer在滑動時停止工作的問題](https://blog.csdn.net/M316625387/article/details/83270639) - 監控應用卡頓 - 性能優化
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看