MJiOS底層筆記--Runloop

本文屬筆記性質,主要針對本身理解不太透徹的地方進行記錄。ios

推薦系統直接學習小碼哥iOS底層原理班---MJ老師的課確實不錯,強推一波。數組


什麼是runloop

iOS系統在程序運行過程當中循環作一些事情bash

應用

  1. 定時器(Timer)、PerformSelector
  2. GCD Async Main Queue
  3. 事件響應、手勢識別、界面刷新
  4. 網絡請求
  5. AutoreleasePool
  6. 其餘

runloop的做用

  1. 保持程序的持續運行
int main(int argc, char * argv[]) {
   @autoreleasepool {
       return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
       
       return 0; 則程序會直接退出。
   }
}
複製代碼
  1. 處理App中的各類事件(好比觸摸事件、定時器事件等)
  2. 節省CPU資源,提升程序性能:該作事時作事,該休息時休息
  3. 其餘

基本概念

  1. 每條線程都有惟一的一個與之對應的RunLoop對象
  2. RunLoop保存在一個全局的Dictionary裏,線程做爲key,RunLoop做爲value
  3. 線程剛建立時並無RunLoop對象,RunLoop會在第一次獲取它時建立
  4. RunLoop會在線程結束時銷燬
  5. 主線程的RunLoop已經自動獲取(建立),子線程默認沒有開啓RunLoop
  6. 任何位置均可以獲取主線程RunLoop
[NSRunLoop currentRunLoop]; // 得到當前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 得到主線程的RunLoop對象
複製代碼

CFRunLoopModeRef

基本知識

  1. CFRunLoopModeRef表明RunLoop的運行模式網絡

  2. 一個RunLoop包含若干個Mode,每一個Mode又包含若干個Source0/Source1/Timer/Observerasync

  3. RunLoop啓動時只能選擇其中一個Mode,做爲currentMode函數

  4. 若是須要切換Mode,只能退出當前Loop,再從新選擇一個Mode進入oop

不一樣組的Source0/Source1/Timer/Observer能分隔開來,互不影響性能

  1. 若是Mode裏沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出

常見Mode

  1. kCFRunLoopDefaultMode(NSDefaultRunLoopMode):

App的默認Mode,一般主線程是在這個Mode下運行學習

  1. UITrackingRunLoopMode:

界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘 Mode 影響ui

  1. UITrackingCommonMode

會進行 _commonModes標記,只要_modes符合 _commonModes都會執行。

_commonModeItems表明着能在Common模式下工做的單元,好比timer。

Source0

  1. 觸摸事件處理
  2. performSelector:onThread:

Source1

  1. 基於Port的線程間通訊
  2. 系統事件捕捉 捕捉以後包裝成Source0去處理

Timers

  1. NSTimer
  2. performSelector:withObject:afterDelay:

底層也是NSTimer

Observers

  1. 用於監聽RunLoop的狀態
  2. UI自動刷新(BeforeWaiting)

runloop休眠以前刷新

  1. Autorelease pool(BeforeWaiting)

清理內部須要釋放的對象

  1. 自主監聽runloop狀態切換
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
        default:
            break;
    }
}


- (void)viewDidLoad {
    // 建立Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放
    CFRelease(observer);
}
複製代碼

RunLoop的運行邏輯

  1. GCD只有在回到主隊列時,纔會依賴runloop
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        // 處理一些子線程的邏輯
        
        // 回到主線程去刷新UI界面
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"11111111111");
        });
    });
}
複製代碼
  1. runloop的休眠是內核層級的休眠,而不是while循環這種死循環


控制線程生命週期(線程保活)

  1. 向自線程runloop中添加一個port,賦予其存在的意義。

線程不會死,也能夠執行test中的代碼。可是run方法裏的end也不會打印,由於runloop已經休眠了。因爲任務永遠不會執行完畢,因此哪怕是VC釋放了,線程也不會釋放。

- (void)viewDidLoad {
    [super viewDidLoad];

    self.thread = [[MJThread alloc] initWithTarget:self selector:@sel(run) obj:nil];
    [self.thread start];
}

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

// 子線程須要執行的任務
- (void)test
{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

- (void)run {
    NSLog(@"%@----begin----", [NSThread currentThread]);
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"%@----end----", [NSThread currentThread]);
}

複製代碼
  1. run

NSRunLoop的run方法是沒法中止的,它專門用於開啓一個永不銷燬的線程(NSRunLoop)。

因此咱們哪怕咱們手動將runloop中止,也是作不到的。

  1. runMode

咱們可使用另一個函數runMode來進行一次runloop循環

但有一個問題是,只要該runloop被喚醒一次,就會退出

[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//[NSDate distantFuture] 會設置一個用不超時的時間
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
複製代碼
  1. while+runMode

經過加標記的方式,讓runloop每次處理完時間,都從新runMode一次

self.thread = [[MJThread alloc] initWithBlock:^{
    // 往RunLoop裏面添加Source\Timer\Observer
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    while (!weakSelf.isStoped) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
}];
複製代碼

解決NSTimer在滑動時中止工做的問題

- (void)viewDidLoad {
    [super viewDidLoad];
    
    static int count = 0;
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%d", ++count);
    }];
//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    // NSDefaultRunLoopMode、UITrackingRunLoopMode纔是真正存在的模式
    // NSRunLoopCommonModes並非一個真的模式,它只是一個標記
    // timer能在_commonModes數組中存放的模式下工做
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

}
複製代碼

performSelector:withObject:afterDelay:

performSelector:withObject:afterDelay:須要依賴定時器與runloop,因此在自線程中並不會起做用。

- (void)test
{
    NSLog(@"2");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"1");
        // 這句代碼的本質是往Runloop中添加定時器
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        NSLog(@"3");

    });
}



//打印
// 1
// 3
複製代碼
相關文章
相關標籤/搜索