小碼哥iOS學習筆記第十七天: Runloop基本認識

1、什麼是Runloop

  • 顧名思義, Runloop就是運行循環, 在程序運行過程當中循環作一些事情bash

  • 應用範疇網絡

    • 定時器(Timer)、PerformSelector
    • GCD Async Main Queue
    • 事件響應、手勢識別、界面刷新
    • 網絡請求
    • AutoreleasePool
  • 一個OC程序, main函數是這樣的app

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
複製代碼
  • 程序打開後, 會一直運行

  • 若是沒有Runloop, main函數返回0
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return 0;
    }
}
複製代碼
  • 此時程序打開後, 會直接關閉, 沒法一直運行

  • 因此能夠寫出Runloop的僞代碼, 以下圖所示

  • do-while是一個死循環, 當沒有任何消息發生的時候, 程序處於睡眠狀態等待消息發生
  • 當消息產生後, 就會處理消息, 接着繼續睡眠等待, 程序並不會立刻退出,而是保持運行狀態
RunLoop的基本做用
保持程序的持續運行
處理App中的各類事件(好比觸摸事件、定時器事件等)
節省CPU資源,提升程序性能:該作事時作事,該休息時休息
......
複製代碼

2、Runloop對象

  • 在iOS中, 有兩套API來訪問和使用Runloop
    • Foundation: NSRunLoop
    • Core Foundation: CFRunLoopRef
  • NSRunLoop和CFRunLoopRef都表明着RunLoop對象

3、RunLoop與線程

  • 每條線程都有惟一的一個與之對應的RunLoop對象
  • RunLoop保存在一個全局的Dictionary裏, 線程做爲key, RunLoop對象作爲Value
  • 線程剛建立時並無RunLoop對象, RunLoop會在第一次獲取它時建立
  • RunLoop會在線程結束時銷燬
  • 主線程的RunLoop已經自動獲取(建立), 子線程默認沒有開啓RunLoop

4、獲取RunLoop對象

  • 能夠經過FoundationCore Foundation來獲取RunLoop

一、Foundation

  • 獲取當前線程的RunLoop
[NSRunLoop currentRunLoop];
複製代碼
  • 獲取主線程的RunLoop
[NSRunLoop mainRunLoop];
複製代碼

二、Core Foundation

  • 獲取當前線程的RunLoop
CFRunLoopGetCurrent()
複製代碼
  • 獲取主線程的RunLoop
CFRunLoopGetMain()
複製代碼

5、RunLoop相關的類

  • RunLoop相關的類有五個
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
複製代碼
  • CFRunLoopRef實際類型是__CFRunLoop

  • 主要成員結構有下面幾個
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
};
複製代碼
  • _modes中存放的是CFRunLoopModeRef類型數據, 其中就有_currentMode, 只不過_currentMode是當前使用的mode函數

  • CFRunLoopModeRef的結構以下oop

  • 主要成員變量有下面幾個
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};
複製代碼
  • 其中的_sources0_sources1CFRunLoopSourceRef類型數據
  • 其中的_observersCFRunLoopObserverRef類型數據
  • 其中的_timersCFRunLoopTimerRef類型數據

6、CFRunLoopModeRef

  • CFRunLoopModeRef表明RunLoop的運行模式
  • 一個RunLoop包含若干個Mode,每一個Mode又包含若干個Source0/Source1/Timer/Observer
  • RunLoop啓動時只能選擇其中一個Mode,做爲currentMode
  • 若是須要切換Mode,只能退出當前Loop,再從新選擇一個Mode進入
    • 不一樣組的Source0/Source1/Timer/Observer能分隔開來,互不影響
  • 若是Mode裏沒有任何Source0/Source1/Timer/ObserverRunLoop會立馬退出

常見的兩種CFRunLoopModeRef

  • kCFRunLoopDefaultModeNSDefaultRunLoopMode): App的默認Mode,一般主線程是在這個Mode下運行
  • UITrackingRunLoopMode: 界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘 Mode 影響

7、RunLoop的運行邏輯

  • Source0
    • 觸摸事件處理
    • performSelector:onThread:
  • Source1
    • 基於Port的線程間通訊
    • 系統事件捕捉
  • Timers
    • NSTimer
    • performSelector:withObject:afterDelay:
  • Observers
    • 用於監聽RunLoop的狀態
    • UI刷新(BeforeWaiting)
    • Autorelease pool(BeforeWaiting)
  • 能夠打印, 當手指點擊屏幕時函數的調用棧

  • 能夠看到屏幕觸摸事件是Source0處理的

8、監聽RunLoop狀態

  • 咱們能夠經過給RunLoop添加Observer的方式監聽RunLoop的狀態, 使用下面這個函數
/** 給RunLoop添加Observer @param rl 目標RunLoop @param observer 須要添加的Observer @param mode 監聽狀態 */
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
複製代碼
  • 能夠經過下面的函數建立Observer
/**
 建立Observer

 @param allocator 分配器
 @param activities 須要監聽的狀態
 @param repeats 是否重複監聽
 @param order 順序
 @param callout 回調函數
 @param context 附加對象
 @return 建立好的Observer
 */
CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context);
複製代碼
  • RunLoop的狀態有下面幾種
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
複製代碼

一、監聽點擊事件

  • 建立一個Observer, 並將其添加到Runloop中, 監聽屏幕點擊事件

  • 運行程序就能夠看到正在監聽RunLoop的狀態, 當最後沒有事件時, Runloop進入睡眠狀態

  • 清空打印, 點擊屏幕, 有以下結果, 能夠看到先進入sources狀態, 而後執行點擊事件

二、監聽滾動事件

  • 監聽UITextView的滾動, 查看RunLoopcurrentMode性能

  • 建立Observer的另外一種方式, 使用block監聽RunLoop狀態ui

  • view上添加一個UITextView

  • 運行程序, 滾動UITextView, 有以下打印

  • 能夠看到滾動以前, 先退出kCFRunLoopDefaultMode, 進入UITrackingRunLoopMode
  • 等滾動結束, 會先退出UITrackingRunLoopMode, 進入kCFRunLoopDefaultMode
相關文章
相關標籤/搜索