淺嘗iOS中RunLoop

今天和大家一起來學(xué)習(xí)一下RunLoop的基本使用,有疏忽的地方,還望各位不吝賜教。


一、RunLoop簡介

RunLoop直接翻譯過來就是運(yùn)行循環(huán)。

  /*
   * 在UIApplicationMain內(nèi)部其實(shí)啟動了一個RunLoop 返回值是一個int類型
   * 所以UIApplicationMain函數(shù)一直沒有返回,從而保證了程序的運(yùn)行
   */
   int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
      }
  }
   /* 反正都寫到這里了,順帶說一句:iOS程序啟動做了啥?
     1、執(zhí)行main函數(shù)
     2、執(zhí)行UIApplicationMain,創(chuàng)建其對象,并設(shè)置其代理
     3、開啟一個事件循環(huán)(主運(yùn)行循環(huán),死循環(huán):保證應(yīng)用程序不會退出)
     4、去加載info.plist(判斷info.plist當(dāng)中有沒有Main,如果有加載Main.storyboard)
     5、應(yīng)用程序啟動完畢(通知代理應(yīng)用程序啟動完畢)
    */

作用

  • 保持程序的持續(xù)運(yùn)行
  • 處理app的各種事件(比如觸摸事件、定時器事件、selector事件)
  • 節(jié)省CPU資源,提高程序的性能:該做事做事,該休息休息

API

  • Foundation框架
    NSRunLoop:NSRunLoop是對CFRunLoopRef一層封裝。
  • coreFoundation框架
    CFRunLoopRef

二、RunLoop和線程的關(guān)系

1、一一對應(yīng)的關(guān)系 是通過字典的形式進(jìn)行關(guān)聯(lián)的 線程為Key RunLoop為value來保存的
2、主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的需要自行創(chuàng)建
3、RunLoop在第一次獲取時創(chuàng)建,在線程結(jié)束時銷毀

    // 獲得主線程對應(yīng)的RunLoop
    [NSRunLoop mainRunLoop];
    CFRunLoopGetMain();
    // 獲得當(dāng)前線程的RunLoop
    [NSRunLoop currentRunLoop];
    CFRunLoopGetCurrent()
    // 相互轉(zhuǎn)換
    mainRunLoop.getCFRunLoop;
    // 主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的需要自行創(chuàng)建
    [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    // run方法的實(shí)現(xiàn)
      - (void)run{
          // 創(chuàng)建子線程對應(yīng)的RunLoop使用currentRunLoop方法來創(chuàng)建,懶加載
          NSLog(@"NSRunLoop-------------------%@",[NSRunLoop currentRunLoop]);
          NSLog(@"NSThread-------------------%@",[NSThread currentThread]);
      }

三、RunLoop相關(guān)類和運(yùn)行模式

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

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

系統(tǒng)的5個Mode

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

  • RunLoop中有多個運(yùn)行模式,但是只能選擇一種模式運(yùn)行
  • 每次RunLoop啟動時,只能指定其中的一個Mode,這個Mode被稱作CurrentMode
  • 如果要切換Mode,只能退出RunLoop,再重新指定Mode進(jìn)入,這樣做是為了分隔開不同組的Source/timer/Observer,互不影響
  • Mode中至少包含一個timer或者是Resource

四、RunLoop--CFRunLoopTimerRef

1、創(chuàng)建定時器方式一

    // 1、創(chuàng)建定時器
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    // 2、添加定時器到runloop,指定runloop為默認(rèn)模式
    /*
     * 第一個參數(shù):定時器
     * 第二個參數(shù):RunLoop的運(yùn)行模式
       * NSDefaultRunLoopMode 如果使用本模式,當(dāng)界面中有拖拽操作的時候,定時器不會工作,因?yàn)镽unLoop切換了運(yùn)行模式為 UITrackingRunLoopMode 界面追蹤模式
       * UITrackingRunLoopMode 如果使用本模式,當(dāng)界面中有拖拽操作的時候,定時器才會工作。
       * 如果想兩種模式同時使用就都添加或者使用NSRunLoopCommonModes
       * NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode
       * 相當(dāng)于把timer添加到NSDefaultRunLoopMode模式下,又添加到UITrackingRunLoopMode模式下面
       * NSRunLoopCommonModes并不是真正意義上的模式,用來占位用的,本身是個標(biāo)簽 凡是添加到NSRunLoopCommonModes中的事件同時會被添加到打上Common標(biāo)簽的運(yùn)行模式上
     */
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    // 3、實(shí)現(xiàn)run方法
    - (void)run{

        NSLog(@"run-------%@--%@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);
    }

2、創(chuàng)建定時器方式二

    // 此方法會自動添加RunLoop中,并且設(shè)置運(yùn)行模式為默認(rèn)
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

3、在子線程調(diào)用定時器

    // 利用NSThread開子線程
    [NSThread detachNewThreadSelector:@selector(timer) toTarget:self withObject:nil];
    // 注意子線程中的RunLoop要手動創(chuàng)建 這里是timer方法的實(shí)現(xiàn)
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    // 給方法會自動添加RunLoop中,并且設(shè)置運(yùn)行模式為默認(rèn)
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    // 開啟RunLoop
    [runloop run];

五、RunLoop--CFRunLoopSourceRef

/*
 * CFRunLoopSourceRef是事件源(輸入源)
 * 以前的分類
 * Port——base Source
 * Custom Input Source
 * Cocoa Perform Selector Source
 *
 * 現(xiàn)在的分類 函數(shù)調(diào)用棧
 * Source0:非基于Port的 用戶自定義的
 * Source1:基于Port的 系統(tǒng)的
 */

六、RunLoop--CFRunLoopObserverRef

/*
 * CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變
 * 可以監(jiān)聽的時間點(diǎn)有以下幾個
 * kCFRunLoopEntry // 即將進(jìn)入RunLoop
 * kCFRunLoopBeforeTimer // 即將處理Timer 
 * kCFRunLoopBeforeSources // 即將處理Sources
 * kCFRunLoopBeforeWaiting // 即將休眠
 * kCFRunLoopAfterWaiting // 即將喚醒
 * kCFRunLoopExit // 即將退出
 */
     /*
     * 第一個參數(shù):怎么分配存儲空間 kCFAllocatorDefault 默認(rèn)方式分配
     * 第二個參數(shù):要監(jiān)聽的狀態(tài) kCFRunLoopAllActivities 所有的狀態(tài)
     * 第三個參數(shù):是否持續(xù)監(jiān)聽 yes表示持續(xù)監(jiān)聽
     * 第四個參數(shù):優(yōu)先級 總是傳遞0
     * 第五個參數(shù):當(dāng)狀態(tài)改變時候的回調(diào)
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        /*
        typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
            kCFRunLoopEntry = (1UL << 0), 即將進(jìn)入RunLoop
            kCFRunLoopBeforeTimers = (1UL << 1), 處理Timer事件
            kCFRunLoopBeforeSources = (1UL << 2), 處理Source事件
            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(@"處理Source事件");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即將進(jìn)入睡眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"被喚醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop退出");
                break;
                
            default:
                break;
        }
});
    
    /*
     * 第一個參數(shù):要監(jiān)聽哪個RunLoop
     * 第二個參數(shù):觀察者
     * 第三個參數(shù):運(yùn)行模式
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//    NSRunLoopCommonModes == kCFRunLoopCommonModes
//     NSDefaultRunLoopMode == kCFRunLoopDefaultMode

七、RunLoop應(yīng)用

/*
 * RunLoop的應(yīng)用(當(dāng)然關(guān)于RunLoop的應(yīng)用當(dāng)然不止這些,筆者也會多多學(xué)習(xí),在以后的文章中分享)
 * NSTimer(上面進(jìn)行了嘗試)
 * 常駐線程(下面展示)
 * 自動釋放池(這個就算了,筆者認(rèn)知也有限,福利一下面試)
     自動釋放池什么時候釋放?
     * RunLoop第一次創(chuàng)建:即將啟動的時候創(chuàng)建
     * RunLoop最后一次銷毀:即將退出的時候
     * 其他時間創(chuàng)建和銷毀:當(dāng)RunLoop即將睡眠的時候銷毀之前的釋放池,重新創(chuàng)建一個新的自動釋放池
 */
 /** 線程屬性 我在界面中添加了兩個按鈕,一個用于創(chuàng)建線程,一個 用于繼續(xù)使用當(dāng)前線程繼續(xù)執(zhí)行任務(wù)*/
@property (nonatomic, strong) NSThread *thread;
 /** 創(chuàng)建線程按鈕執(zhí)行的方法 */
  self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(task1) object:nil];
  [self.thread start];
/** task1任務(wù)實(shí)現(xiàn) */
- (void)task1{
    // 同一個線程中任務(wù)是串行執(zhí)行的
    NSLog(@"執(zhí)行task1");

    // 解決方法,開一個RunLoop,如果沒有這個RunLoop self.thread會進(jìn)入死亡狀態(tài),無法再繼續(xù)執(zhí)行任務(wù)2
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    
    // 如果沒有timer或者source 添加保證不讓RunLoop退出
    [runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    
    // 開啟
    //    [runloop run];
    // 僅限于子線程的RunLoop,主線程沒有效果
    [runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]];
}
 /** 繼續(xù)執(zhí)行任務(wù)按鈕方法 */
  // 直接調(diào)動會崩潰,因?yàn)殡m然self.thread被強(qiáng)指針指向,但是當(dāng)任務(wù)1執(zhí)行完畢后,self.thread已經(jīng)進(jìn)入死亡狀態(tài),無法再繼續(xù)執(zhí)行任務(wù)2
   [self performSelector:@selector(task2) onThread:self.thread withObject:nil waitUntilDone:YES];
 /** task2任務(wù)實(shí)現(xiàn) */
  - (void)task2{

      NSLog(@"執(zhí)行task2");
  }

寫在最后的話:關(guān)于iOS-RunLoop的知識今天就學(xué)習(xí)到這里,關(guān)于iOS-RunLoop方面的問題歡迎大家和我交流,共同進(jìn)步,謝謝各位。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容