RunLoop由淺到深,個人小結(jié)

文藝求關(guān)注.png
  • <h5>初識RunLoop</h5>
    • 從字面意義看:運(yùn)行循環(huán)
  • <b>基本作用</b>
    • 保持程序的運(yùn)行循環(huán)
    • 處理app中的各種時間(比如觸摸事件、定時器事件、selector事件)
    • 節(jié)省CPU資源,提高程序性能:該做事時做事,該休息時休息
    • . . . . . . .
  • <b>main函數(shù)中的runLoop</b>
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
  • UIApplicationMain函數(shù)內(nèi)部就啟動了一個RunLoop
  • 所以UIApplication函數(shù)一直沒有返回,保持了程序的持續(xù)運(yùn)行
  • 這個默認(rèn)啟動的RunLoop是跟主線程相關(guān)聯(lián)的

  • <h5>RunLoop對象</h5>
    • iOS中有2套API來訪問和使用RunLoop
      • Foundation
        - NSRunLoop
      • Core Foundation
        - CFRunLoopRef
    • NSRunLoop和CFRunLoopRef都代表著RunLoop對象
    • NSRunLoop是基于CFRunLoopRef的一層OC包裝,所以要了解RunLoop內(nèi)部結(jié)構(gòu),需要多研究CFRunLoopRef層面的API(Core Foundation層面)

  • <h5>RunLoop與線程間關(guān)系</h5>
    • 每條線程都有唯一的一個與之對應(yīng)的RunLoop對象
    • 主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建
    • RunLoop在第一次獲取時創(chuàng)建,在線程結(jié)束時銷毀
  • <h5>獲得RunLoop對象的方法</h5>
    • Foundation
      [NSRunLoop currentRunLoop]; //獲得當(dāng)前線程的RunLoop對象
      [NSRunLoop mainRunLoop]; //獲得主線程的RunLoop對象
    • Core Foundation
      CFRunLoopGetCurrent(); //獲得當(dāng)前線程的RunLoop對象
      CFRunLoopGetMain(); //獲得主線程的RunLoop對象
    • 如何轉(zhuǎn)化如上得到的兩個RunLoop函數(shù),使其地址一樣
      mainRunLoop.getCFRunLoop

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
}

- (void) run {
    // 如何創(chuàng)建子線程對應(yīng)的RunLoop,比較奇葩
    // currentRunLoop本身是懶加載,會在第一次調(diào)用的時候判斷是否存在,如不存在,自動創(chuàng)建
    NSLog(@"%@", [NSRunLoop currentRunLoop]);
    NSLog(@"run ---------- %@", [NSThread currentThread]);
}

  • <h5>RunLoop的相關(guān)類</h5>
    • Core Foundation中關(guān)于RunLoop的5個類
      CFRunLoopRef
      CFRunLoopModeRef //RunLoop的運(yùn)行模式
      CFRunLoopSourceRef // 數(shù)據(jù)源、事件源
      CFRunLoopTimerRef //NSTimer
      CFRunLoopObserverRef // 觀察、監(jiān)聽RunLoop
RunLoop5各類對應(yīng)關(guān)系.png

<b>* 注意點(diǎn):</b>

  • 在RunLoop中有多個運(yùn)行模式,但是RunLoop只能選擇一種模式運(yùn)行
  • mode里面至少要有一個timer或者source,observer可有可無(只是用來觀察/監(jiān)聽RunLoop)

  • <b>CFRunLoopModeRef</b>
    • <b>CFRunLoopModeRef代表RunLoop的運(yùn)行模式</b>
      • 一個RunLoop包含若干個Mode,每個Mode又包含若干個Source/Timer/Observer
      • 每次RunLoop啟動時,只能指定其中一個Mode,這個Mode被稱作CurrentMode
      • 這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
    • <b>系統(tǒng)默認(rèn)注冊了5個Mode</b>
      • kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個Mode下運(yùn)行(常用)
      • UITrackingRunLoopMode: 界面追蹤Mode,用于tableView、ScrollView追蹤觸摸滑動,保證界面滑動時不受其他Mode影響(常用)
      • UIInitializationRunLoopMode:在剛啟動App時,進(jìn)入的第一個mode,啟動后就不再使用
      • GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部Mode,通常用不到
      • kCFRunLoopCommonModes:這是一個占位用的Mode,不是真正的Mode
  • <h5> CFRunLoopModeRef使用</h5>
- (void)timer {
    // 1.創(chuàng)建定時器
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    /**
     2.添加定時器到RunLoop中,指定runLoop的運(yùn)行模式
     @param NSTimer 定時器
     @param Mode runLoop的運(yùn)行模式
     */
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    //UITrackingRunLoopMode: 界面追蹤
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    //NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode
    //占用,標(biāo)簽,凡是添加到NSRunLoopCommonModes中的事件都會被同時添加到打上common的運(yùn)行模式上
    /**
     0 : <CFString 0x10af41270 [0x10a0457b0]>{contents =
         "UITrackingRunLoopMode"}
     2 : <CFString 0x10a065b60 [0x10a0457b0]>{contents = "kCFRunLoopDefaultMode"}
     */
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];  
}
- (void) run {
    NSLog(@"run -----------%@------%@", [NSThread currentThread], [NSRunLoop currentRunLoop]);
}
  • <b>子線程中使用RunLoop的注意點(diǎn)</b>
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [NSThread detachNewThreadSelector:@selector(timer) toTarget:self withObject:nil];
}
- (void)timer {
    
    // 如果是在子線程中使用此timer創(chuàng)建方式,則需要手動創(chuàng)建runLoop
    NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
    
    // 該方法內(nèi)部自動添加到runLoop中,并且設(shè)置運(yùn)行模式為默認(rèn)
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    
    // 子線程中創(chuàng)建runLoop之后不會執(zhí)行,需要手動開啟runLoop
    [currentRunLoop run];
}

  • <h5>定時器補(bǔ)充—不受RunLoop影響的定時器(GCD定時器)</h5>
@property (nonatomic, strong) dispatch_source_t timer;

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    /**
     01.創(chuàng)建GCD中的定時器
     @param DISPATCH_SOURCE_TYPE_TIMER source的類型,表示是定時器
     @param 0 描述信息,線程ID
     @param 0 更詳細(xì)的描述信息
     @param dispatchQueue 隊(duì)列,確定GCD定時器中的任務(wù)在哪個線程中執(zhí)行
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    
    /**
     02.設(shè)置定時器(起始時間|間隔時間|精準(zhǔn)度)
     @param timer 定時器對象
     @param DISPATCH_TIME_NOW 起始時間,DISPATCH_TIME_NOW 從現(xiàn)在開始計(jì)時
     @param intervalInSeconds * NSEC_PER_SEC 間隔時間
     @param leewayInSeconds * NSEC_PER_SEC 精準(zhǔn)度,絕對精準(zhǔn)0
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    
    // 03.設(shè)置定時器執(zhí)行的任務(wù)
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"------------%@", [NSThread currentThread]);
    });
    
    // 04.啟動執(zhí)行
    dispatch_resume(timer);
    
    self.timer = timer;
}
  • <b>注意點(diǎn):</b>
    • 1.GCD的定時器與NSTimer的定時器相比,GCD的定時器時間更為精準(zhǔn)
    • 2.GCD的定時器完全不受RunLoop Mode的影響

  • <h5>CFRunLoopSourceRef</h5>
    • CFRunLoopSourceRef是事件源(輸入源)

    • 以前的分法

      • Port-Based Sources //基于端口的事件
      • Custom Input Sources //自定義事件
      • Cocoa Perform Selector Sources //perform Selector
    • 現(xiàn)在的分發(fā):(通過函數(shù)調(diào)用站的方式來分)

      • Source0:非基于Port的 //用戶觸發(fā)的事件
      • Source1:基于Port的 //系統(tǒng)內(nèi)部的消息事件

  • <h5>CFRunLoopObserverRef</h5>
- (void) observer {
    /**
     // 01.創(chuàng)建監(jiān)聽者
     @param allocator 制定如何分配存儲空間
     @param activities 要監(jiān)聽的狀態(tài) kCFRunLoopAllActivities 所有狀態(tài)
     @param repeats 是否持續(xù)監(jiān)聽
     @param order 優(yōu)先級,總是傳0
     @param observer activity 當(dāng)狀態(tài)改變的時候回調(diào)
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        /**
         kCFRunLoopEntry = (1UL << 0),          //即將進(jìn)入runLoop
         kCFRunLoopBeforeTimers = (1UL << 1),   //即將處理timer事件
         kCFRunLoopBeforeSources = (1UL << 2),  //即將處理sources事件
         kCFRunLoopBeforeWaiting = (1UL << 5),  //即將進(jìn)入睡眠
         kCFRunLoopAfterWaiting = (1UL << 6),   //被喚醒
         kCFRunLoopExit = (1UL << 7),           //runloop推出
         kCFRunLoopAllActivities = 0x0FFFFFFFU  //所有狀態(tài)
         */
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即將進(jìn)入runLoop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即將處理timer事件");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即將處理sources事件");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即將進(jìn)入睡眠");
                break;
            case kCFRunLoopExit:
                NSLog(@"runloop推出" );
                break;
            case kCFRunLoopAllActivities:
                NSLog(@"所有狀態(tài)");
                break;
            default:
                break;
        }
    });
}

  • <h5>RunLoop處理邏輯—官方版</h5>


    RunLoop處理邏輯-官方版.png
  • <b>RunLoop的事件隊(duì)列</b>
    每次運(yùn)行run loop,你的線程的run loop會自動處理之前未處理的消息,并通知相關(guān)的觀察者。具體順序如下:
    • 1.通知觀察者run loop已經(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.處理為處理的事件
      • 如果用戶定義的定時器啟動,處理定時器事件并重啟run loop。進(jìn)入步驟2
      • 如果輸入源啟動,傳遞相應(yīng)的消息
      • 如果run loop被顯示喚醒而且時間還未超時,重啟run loop。進(jìn)入步驟2
    • 10.通知觀察者run loop結(jié)束

  • <h5>RunLoop處理邏輯—網(wǎng)友整理版</h5>


    RunLoop處理邏輯-網(wǎng)友整理版.png

  • <h5>RunLoop應(yīng)用—常駐線程</h5>
    RunLoop在開發(fā)中有很多應(yīng)用場景,比如說:處理NSTimer不工作,ImageView的顯示,PerformSelector事件,常駐線程,以及自動釋放池等
  • <b>常駐線程</b>
@property (nonatomic, strong) NSThread *thread;
// 在storyboard中有兩個按鈕,創(chuàng)建線程、讓線程繼續(xù)執(zhí)行任務(wù)
//需求:如何保證子線程不掛掉,同時執(zhí)行其他任務(wù)
- (IBAction)creatThreadBtnClick:(id)sender {
    // 01.創(chuàng)建線程
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(task1) object:nil];
    [self.thread start];
}
- (void)task1 {
    NSLog(@"task1-----------%@", [NSThread currentThread]);
// 解決方法(保證子線程不掛掉,同時執(zhí)行其他任務(wù)):RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    //保證RunLoop不退出
    // 方法一:添加Timer
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runOperation) userInfo:nil repeats:YES];
    [runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
    // 方法二:添加Source
    [runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];    // 給runLoop設(shè)置時間,10秒鐘后runLoop自動退出,(該設(shè)置只針對于子線程中的runLoop有用,對主線程中的runLoop沒有任何作用)
    NSLog(@"--------end---------"); // 10秒鐘之后打印該內(nèi)容
}
- (void)runOperation {

    NSLog(@"%s", __func__);
}
- (IBAction)otherOperationBtnClick:(id)sender {
    [self performSelector:@selector(task2) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)task2 {
    NSLog(@"task2-----------%@", [NSThread currentThread]);
}

  • <h5>RunLoop常見面試題</h5>
    • 什么是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(Sources0、Sources1)、Timer,那么就直接退出RunLoop
    • 自動釋放吃什么時候創(chuàng)建?什么時候釋放?
      • 第一次創(chuàng)建在:啟動runLoop的時候
      • 最后一次銷毀在:runLoop退出的時候
      • 其他時候的創(chuàng)建和銷毀:當(dāng)runLoop即將睡眠的時候銷毀之前的釋放池,并重新創(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)擊事件之前做一些事情)
關(guān)注一下又不會懷孕.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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