iOS多線程-RunLoop簡介

  • 什麼是RunLoop?

    • 從字面上來看是運行循環的意思.

    • 內部就是一個do{}while循環,在這個循環裏內部不斷的處理各類任務(好比:source/timer/Observer)

    • RunLoop的存在其實就是爲線程而存在的.線程的做用就是執行一個特定的任務,可是默認狀況下線程執行完任務後就不能再次執行任務,這是由於默認狀況下線程是沒有開啓RunLoop的.若是開啓RunLoop以後,線程執行完任務以後,會一直等待,直到再次接受到任務,接續執行任務.線程銷燬前,會先釋放這個線程所對應的RunLoop.

  • RunLoop基本做用

    • 保持程序的持續運行,保持線程的持續運行.

    • 處理App中的各類事件(好比觸摸事件,定時器事件,Selector事件)

    • 節省CPU資源,提升程序性能:該作事時作事,該休息時休息

  • RunLoop對象

    • ios中有2套API來訪問和使用RunLoop

    • 一套是Fundation(純OC的)框架中的

      • NSRunLoop
        // 得到當前線程的RunLoop對象 [NSRunLoop currentRunLoop]; // 得到主線程的RunLoop對象 [NSRunLoop mainRunLoop];
    • 一套是Core Fundation(純C語言的)框架中的

      • CFRunLoopRef
        // 得到當前線程的RunLoop對象 CFRunLoopGetCurrent(); // 得到主線程的RunLoop對象 CFRunLoopGetMain();
    • NSRunLoo和CFRunLoopRef都表明着RunLoop對象.NSRunLoop是基於CFRunLoopRef的一層OC包裝

  • RunLoop與線程

    • 每條線程都有惟一的一個與之對應的RunLoop對象

    • 主線程的Runloop系統已經自動建立好了,子線程的RunLoop須要手動建立

    • RunLoop在第一次獲取時由系統自動建立,在線程結束時銷燬

    • 若是想給子線程建立RunLoop,不能直接alloc&init,只要調用獲取當前線程RunLoop方法便可,系統會自動放回當前線程的RunLoop,若是當前線程沒有RunLoop,系統會自動建立.

  • RunLoop相關類

  • Core Fundation中關於RunLoop的5個類

    • CFRunLoopRef: RunLoop對象
    • CFRunLoopModeRef: RunLoop運行模式.
    • CFRunLoopSoruceRef: 事件源(輸入源)
    • CFRunLoopTimerRef:基於時間的觸發器.
    • CFRunLoopObserverRef: 觀察者,可以監聽RunLoop的狀態改變
  • CFRunLoopModeRef

  • CFRunLoopModeRef表明RunLoop運行模式

  • 一個RunLoop對象包含若干個Mode(模式),每一個Mode又包含若干個 source/Timer/Observer

  • RunLoop運行時,只能指定一個Mode, 這個Mode又稱之爲CurrentMode,而後RunLoo就執行CurrentMode中的source/Timer/Observer

  • 若是須要切換Mode,只能退出RunLoop,再從新指定一個Mode進入,這樣作是爲了分隔開不一樣組的Source/Timer/Observer,讓其不受影響

  • 系統默認註冊了5個Mode:

    • NSDefaultRunLoopMode: App的默認Mode,一般主線程實在這個模式下運行
    • UITrackingRunLoopMode:界面跟蹤Mode,用於界面控件(ScrollView,tableView等等)追蹤觸摸滑動,保證界面滑動時不受其餘Mode影響
    • UIInitializationRunLoopMode:在剛啓動App是進入的第一個Mode,啓動完成後就再也不使用
    • GSEventReceiveRunLoopMode:接收系統事件的內部Mode,一般用不到
    • NSRunLoopCommonMode:這是一個佔位的Mode,不是一種真正的Mode,(能夠當作模式組,默認狀況下包括了NSDefaultRunLoopMode,UITrackingRunLoopMode)兩種模式.
  • CFRunLoopTimerRef

    • CFRunLoopTimerRef是基於時間的觸發器

    • CFRunLoopTimerRef基本上說的就是NSTimer,它受RunLoop的Mode影響

      -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //建立一個NSTimer定時器,默認狀況下NSTimer是不會執行的,只有把NSTimer添加到RunLoop中,由RunLoop管理執行 NSTimer * timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(show) userInfo:nil repeats:YES]; // 在當前線程中RunLoop添加一個timer, 並告訴runLoop, 這個timer只能在NSDefaultRunLoopMode模式下才能觸發 // runLoop會找到NSDefaultRunLoopMode,而後把timer添加NSDefaultRunLoopMode中的Timer數組中 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; //在當前線程中RunLoop添加一個timer, 並告訴runLoop, 這個timer只能在NSRunLoopCommonModes模式下才能觸發 //runLoop會找到NSDefaultRunLoopMode和UITrackingRunLoopMode //而後把timer添加NSDefaultRunLoopMode和UITrackingRunLoopMode中的Timer數組中 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; //利用此方法建立的NSTimer, 系統會自動放入當前線程中的currentRunLoop中,而且只能在NSDefaultRunLoop模式下才能觸發 NSTimer * timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES]; //雖然經過類方法scheduledTimerWithTimeInterval建立NSTimer,會自動添加到NSDefaultRunLoopMode模式中 //但咱們仍是能夠修改它的模式 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; }
  • CFRunLoopSoruceRef

    • 按照官方文檔,source的分類:

      • Port-Based Sources:基於端口的事件源:監聽程序響應的端口,基於端口事件是由系統內核自動發送的.
      • Custom Input Sources: 自定義輸入源:監聽自定義事件源,而自定義的輸入源是須要人工從其餘線程發送
      • Cocoa Perfrom Selector Source: selector事件源
    • 按照源碼函數調用棧,source的分類:

      • Source0:非基於Prot(端口)的,是用戶主動觸發的事件
      • Source1:基於Prot(端口)的,經過內核和其餘線程相互發送消息
  • CFRunLoopObserverRef

    • CFRunLoopObserverRef:觀察者對象,能夠監聽RunLoop的狀態

    • RunLoop狀態:

      • kCFRunLoopEntry 即將進入runLoop
      • kCFRunLoopBeforeTimers 即將處理Timer
      • kCFRunLoopBeforeSources 即將處理source(事件源)
      • kCFRunLoopBeforeWaiting 即將進入休眠
      • kCFRunLoopAfterWaiting 即將從休眠中醒來
      • kCFRunLoopExit 即將退出runLoop
      -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //建立一個CFRunLoopObserverRef /* 第一個參數: CFRunLoopObserverRef(觀察者)分配內存空間方式 第二個參數: 監聽那些狀態 kCFRunLoopAllActivities(監聽全部狀態) 第三個參數: 是否每次都要監聽 第四個參數: 優先級 第五個參數: 回調函數 */ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES,0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { // observer監聽對象 //activity Runloop當前狀態 }); /* 第一個參數: 爲那個線程下的RunLoop添加CFRunLoopObserverRef(觀察者) 第二個參數: 須要添加的CFRunLoopObserverRef(觀察者) 第三個參數: 把監聽添加到RunLoop那個模式中 */ CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); //記得內存管理,由於Core Foundation不在ARC管理範圍內 //帶有Create、Copy、Retain等字眼的函數,建立出來的對象,都須要在最後作一次release //銷燬對象函數:CFRelease對象 CFRelease(observer); }
  • RunLoop處理邏輯

  • 若是RunLoop中沒有Timer或source,RunLoop就會馬上退出

  • 每次運行RunLoop,RunLoop會自動處理以前未處理的消息,並通知相關觀察者.具體順序以下:

    • 1.通知觀察者RunLoop已經啓動

    • 2.通知觀察者即將開始啓動定時器

    • 3.通知觀察者即將啓動非基於端口的事件源

    • 4.啓動任何準備好的非基於端口的事件源

    • 5.若是基於端口的事件源準備好並處於等待得狀態,當即啓動.並進入步驟9

    • 6.通知觀察者線程進入休眠

    • 7.將線程置於休眠直到任意下面的事件發生:

      • 某一事件到達基於端口的源
      • 定時器啓動
      • RunLoop設置的時間已經超時.(系統底層會給RunLoop設置一個超時時間,源碼中設置的是:9999999999.0)
      • RunLoop被手動喚醒
    • 8.通知觀察者線程將被喚醒.

    • 9.處理未處理的事件

      • 若是用戶定義的定時器啓動,處理定時器事件並重啓RunLoop.進入步驟2
      • 若是事件源啓動,傳遞相應的消息
      • 若是RunLooop被顯示喚醒並且時間還沒超時,重啓RunLoop.進入步驟2
    • 10.通知觀察者RunLoop結束.

  • 如何讓子線程成爲常駐線程(讓一個子線程不進入消亡狀態,等待其餘線程發來消息,處理其餘事件)

// // ViewControllerRunLoop.m // // Created by yuxuan on 16/2/17. // Copyright © 2016年 apple. All rights reserved. // #import "ViewControllerRunLoop.h" @interface ViewControllerRunLoop () @property(nonatomic,strong)NSThread * thread; @end @implementation ViewControllerRunLoop -(void)viewDidLoad{ //建立子線程執行任務 self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [self.thread start]; } -(void)run{ NSLog(@"跑起來"); //默認狀況下,子線程是不會常駐的 //只有子線程中runloop啓動,而且runloop中有source或timer,纔會常駐 //只有常駐線程才能再次執行任務,由於線程中有runloop來處理事件了 //子線程的runloop是須要手動建立的, 而且須要手動啓動 NSRunLoop * rl = [NSRunLoop currentRunLoop]; //若是子線程的runloop沒有 source / timer 的話, 哪麼子線程的runloop會當即關閉 //在runLoop中添加一個timer [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timerRun) userInfo:nil repeats:YES]; //啓動runloop [rl run]; //若是線程成爲了常駐線程,你會發現,不會執行到這行代碼 //也就是說這個方法不會執行完, NSLog(@"end"); } -(void)timerRun{ NSLog(@"%s",__func__); } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ // 讓子線程再次執行任務 [self performSelector:@selector(againRun) onThread:self.thread withObject:nil waitUntilDone:NO]; } -(void)againRun{ NSLog(@"再次跑起來"); } @end



原文連接:http://www.jianshu.com/p/94d61de9e139
相關文章
相關標籤/搜索