iOS RunLoop

RunLoop 應用:NSTimer、 PerformSelector、常駐線程
iOS 中有兩套API訪問 Foundation(NSRunLoop), CoreFoundation CFRunLoopRef

一、RunLoop 引入

  • 在程序的啟動入口Main 函數中,UIApplicationMain 函數內部就啟動了一個與主線程相關聯的RunLoop
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
  • RunLoop 字面意義上:跑圈(那我可不可以看成是死循環呢),那么來驗證一下吧
int main(int argc, char * argv[]) {
   @autoreleasepool {
       
       // 查看了UIApplicationMain 的返回值是int類型
//        UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);

       NSLog(@"welcome to Runloop");
       int jjTest = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
       NSLog(@"快到這里來");
       return jjTest;
   }
}
控制的輸出是這樣的.png
  • 從上可以看出main 函數里面啟動了個RunLoop,而且是保持運行的狀態

二、RunLoop里面到底有啥呢?

  • 上面說過RunLoop 和 主線程的關系,那線程和RunLoop 是什么關系呢?
    • 每條線程都有一個唯一的與之對應的RunLoop對象
    • Foundation:
      • 1.獲取當前線程RunLoop:[NSRunLoop currentRunLoop]
      • 2.獲取主線程RunLoop:[NSRunLoop mainRunLoop]
    • CoreFoundation:
      • 1.獲取當前線程RunLoop:CFRunLoopGetCurrent()
      • 2.獲取主線程RunLoop:CFRunLoopGetMain()
  • 了解一個對象的最好方式就是打印出來看看:
RunLoop 對象
  • 我的天該怎么看:JJ看到的貌似是一些CFRunLoopObserver CFRunLoopSource, 還有timers 等對象,打印出來發現還是有些復雜了,what should i do~

  • 那就只能看看官方文檔有關RunLoop的描述了,如果英文不好的就繼續往下

  • 根據文檔的描述:RunLoop相關聯的類有五個(再看看打印出來的內容)

    • CFRunLoopRef
    • CFRunLoopSourceRef
    • CFRunLoopObserverRef
    • CFRunLoopTimerRef
    • CFRunLoopModeRef
  • RunLoop 中對象

RunLoop 對象圖示

三、RunLoop相關類的理解

  • 1.從上圖可以看出

    • 一個RunLoop 可以有多個Mode(模式),每個模式又包含若干個 Source/Observer/Timer
    • RunLoop 一次只能運行一種Mode,切換Mode需要退出當前Mode
  • CFRunLoopModeRef 一共有五種模式

    • kCFRunLoopDefaultMode 默認模式,通常主線程在這個模式下運行
    • kCFRunLoopCommonModes 占位符(表示)
    • UITrackingRunLoopMode 界面跟蹤Mode,用于追蹤Scrollview的觸摸滑動
    • UIInitializationRunLoopMode:剛啟動App時進入的第一個Mode,啟動后不在使用。
    • GSEventReceiveRunLoop:內部Mode,接收系事件。
  • CFRunLoopSourceRef(函數調用棧)

    • Source0:非基于Port(處理事件)
    • Source1:基于Port (接收分發系統事件)
    • 觸摸事件:
 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   NSLog(@"-------");
}
  • 觸摸事件的函數調用棧調用順序入下圖:
不難看出Source0、1的作用
  • CFRunLoopTimerRef:基于時間的觸發器(和NSTimer差不多)

  • CFRunLoopObserverRef:監聽RunLoop狀態的觀察者

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), // 1
    kCFRunLoopBeforeTimers = (1UL << 1), // 2
    kCFRunLoopBeforeSources = (1UL << 2), // 4
    kCFRunLoopBeforeWaiting = (1UL << 5), // 32
    kCFRunLoopAfterWaiting = (1UL << 6), // 64
    kCFRunLoopExit = (1UL << 7), // 128
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
  • RunLoop 處理邏輯
轉載

四、RunLoop 使用

  • 監聽RunLoop:監聽RunLoop的狀態 用途:在RunLoop喚醒前做些操作

    • 創建Observer:CFRunLoopObserverCreateWithHandler
    • 添加觀察者 :CFRunLoopAddObserver
    • 釋放Observer:CFRelease
      • 本例ARC狀態,ARC只能對OC對象進行內存管理,C中的對象需要自行管理
      // 創建observer
      CFRunLoopObserverRef obser = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
          NSLog(@"-----Runloop狀態------%zd", activity);
      });
      
      // 添加觀察者:監聽RunLoop的狀態
      CFRunLoopAddObserver(CFRunLoopGetMain(), obser, kCFRunLoopDefaultMode);
      
      // 釋放Observer
      CFRelease(obser);
    
  • 在RunLoop中創建定時器:

    • 定時器NSTimer的創建方式一:timerWithTimeInterval:
      • 創建timer
      • 將timer 添加到RunLoop中
      NSTimer *timer =[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(JYTest) userInfo:nil repeats:YES];
      // 定時器只運行在NSDefaultRunLoopMode下,一旦RunLoop進入其他模式,這個定時器就不會工作
      //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
      // 定時器只運行在UITrackingRunLoopMode下,一旦RunLoop進入其他模式,這個定時器就不會工作
      //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
      // 標記為common modes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode
      [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    

}

- 定時器NSTimer的創建方式二:scheduledTimerWithTimeInterval
  - 創建timer:此方法是自動將timer 添加到線程中
  - 注意NSTimer 在`子線程創建timer` 需要手動開啟當前線程的RunLoop

```objc
- (void)scheduledTimer {
  // 調用了
  NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(JYTest) userInfo:nil repeats:YES];
  
  // 改變Timer的模式調用了scheduledTimer返回的定時器,已經自動被添加到當前runLoop中,而且是NSDefaultRunLoopMode
  [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void) JYTest {
  
  NSLog(@"----0 0 ");
}
  • 需求一 開辟一個不死線程來處理自定義的一些事件(常駐線程)

    • 如何讓一個線程處理一個任務之后不死呢?
    • 答:主線程是通過創建RunLoop 使得主線程不死,一直處理事件。所以創建一個子線程,并創建子線程與之對應的RunLoop
  • 創建一個不死線程:

    • 創建一個子線程
    • 為了監聽線程是否被銷毀,本例自定了一個JJThread,并重寫dealloc 方法
// 將線程定義成屬性
@property (nonatomic, strong) JJThread *thread;


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    self.thread = [[JJThread alloc] initWithTarget:self selector:@selector(jjTest) object:nil];
    [self.thread start];
    
}
  • 創建RunLoop,方式一
    • 1.前面說過RunLoop中必須有source,Observer,Timer 其中的任一一個,若為空則執行一次RunLoop,就退出Runloop
    • 2.stackoverflow解釋上面說的這個問題
- (void)jjTest {
    
    // NSRunloop 中必須要有timer, source, observer 一個或者多個,不然會導致
    NSLog(@"-----jjTest-------%@", [NSThread currentThread]);

    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    
    NSLog(@"-----jjTest-------%@", [NSThread currentThread]);

    
    // http://stackoverflow.com/questions/31199802/what-does-nsrunloop-currentrunloop-runmodensdefaultrunloopmode-beforedate
}
  • 創建RunLoop,方式二

    • 上面說RunLoop 必須要有中必須要有timer, source, observer 一個或者多個,這里為什么沒有源還行呢。
    • 答:因為RunLoop一直在執行退出,進入的狀態,這個中間若是接收到performSelector 發放不就有source,可以嘗試一下
    • 注意點:while 條件中會定義一個BOOL 的屬性,做標記管理RunLoop 是否退出死循環。
- (void)jjTest {
    
    NSLog(@"----------run----%@", [NSThread currentThread]);
    
    while (1) {
        [[NSRunLoop currentRunLoop] run];
    }
}


- (void)jjRun {
    
    NSLog(@"------jjRun------%@", [NSThread currentThread]);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    [self performSelector:@selector(jjRun) onThread:self.thread withObject:nil waitUntilDone:NO];
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容