RunLoop

RunLoop

RunLoop概述

什么是RunLoop

  • RunLoop從字面意思看是運行循環(huán),跑圈的意思,實際蘋果為了方便理解也大體上就是這個意思
  • RunLoop的基本作用:
    • 1.保持程序的持續(xù)你運作,處理各種事件(觸摸事件,定時器事件,監(jiān)聽事件等)
    • 2.節(jié)省CPU資源,提高程序性能,有事件做事件,沒事休息
    • 3.RunLoop只要在運行的過程中就會不斷的去查看有沒有事情可以做,但是如果根本就沒有各種事件的對象,也就是RunLoop里面的mode沒有任何屬性的話,RunLoop一跑起來就會結(jié)束
  • RunLoop的大致的簡單代碼
int main(int argc, char * argv[]) {
    BOOL running = YES;
    do {
        //執(zhí)行各種任務,處理事件
    } while (running);
    return 0;
}
  • 由于main函數(shù)里面調(diào)用了UIApplicationMain的函數(shù),而這個函數(shù)內(nèi)就會啟動一個RunLoop,并且這個RunLoop是跟主線程相關(guān)聯(lián)的,所以程序不會馬上退出,會保持運行下去

RunLoop對象

  • 在ios中可以通過兩個api來訪問RunLoop
  • RunLoop與線程的關(guān)系:每一個線程都會有唯一一個RunLoop與之對應,實際內(nèi)部是以字典的形式管理,主線程的RunLoop已經(jīng)自己創(chuàng)建好了,子線程的需要手動創(chuàng)建,創(chuàng)建好的RunLoop會一直存在,直到對應線程結(jié)束才會銷毀
    //獲得當前線程的RunLoop對象
    [NSRunLoop currentRunLoop];
    //獲取主線程的RunLoop對象
    [NSRunLoop mainRunLoop];

RunLoop的五個類

Core Foundation中關(guān)于RunLoop的5個類

  • CFRunLoopRef(RunLoop類)
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoopModeRef

  • CFRunLoopModeRef代表RunLoop的運行模式,一個RunLoop可以有許多個mode,每一個mode又可以有許多個Source/Timer/Observer
  • 每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode
  • 如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入
  • 這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
  • 系統(tǒng)默認注冊了5個Mode:
    • kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
    • UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
    • UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
    • GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
    • kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode
//schedule方法會默認把timer的事件加入RunLoop中,timerWith的方法不會
    //RunLoop在運行中只會指定一個mode,若果切換模式要停止RunLoop,并重新開始新的模式下的RunLoop
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    // 定時器默認只運行在NSDefaultRunLoopMode下,一旦RunLoop進入其他模式,這個定時器就不會工作
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    // 定時器會跑在標記為common modes的模式下
    // 標記為common modes的模式:UITrackingRunLoopMode和kCFRunLoopDefaultMode
    //如果mode設(shè)置為common,那么表示定時器在這兩個mode下都可以跑
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    - (void)run
{
    NSLog(@"----run");
}

CFRunLoopSourceRef

  • CFRunLoopSourceRef被稱為事件源,輸入源,也就是RunLoop所處理的觸摸事件
  • 對于CFRunLoopSourceRef,我們可以有兩種不同的分類方法
  • 1.按照蘋果官方文檔分類
    • Port-Based Sources:基于端口的,通過內(nèi)核發(fā)布的
    • Custom Input Sources:自定義的輸入源
    • Cocoa Perform Selector Sources:處理Perform開頭的方法,也就是所有Perform方法都是通過這個輸入源來處理的
  • 2.按照函數(shù)調(diào)用棧分類
    • Source0:非基于Port的
    • Source1:基于Port,通過內(nèi)核和其他線程通訊的,接收或者分發(fā)系統(tǒng)的事件
  • 下面的圖就是函數(shù)調(diào)用棧:程序一啟動就會在底層調(diào)用許多函數(shù),自下而上,最后才在當前方法的控制器中調(diào)用當前方法,而且我們也可以看出這個函數(shù)調(diào)用棧是隸屬于一個線程的,而這個線程是隸屬于RunLoop的
3.png

CFRunLoopObserverRef

  • CFRunLoopObserverRef是觀察者,在RunLoop中是用來監(jiān)聽RunLoop的狀態(tài)變化的
- (void)observer
{
    // 創(chuàng)建observer(創(chuàng)建CFRunLoopObserverRef必須用到CF庫)
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    //在這個block中可以看到RunLoop的活動狀態(tài),CFRunLoopActivity就是RunLoop的狀態(tài)
        NSLog(@"----監(jiān)聽到RunLoop狀態(tài)發(fā)生改變---%zd", activity);
    });

    // 添加觀察者:監(jiān)聽RunLoop的狀態(tài)
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

    // 釋放Observer(由于是CF框架下得,不是oc的框架,所以要手動釋放)
    CFRelease(observer);
    //CFRunLoopActivity的枚舉值
    kCFRunLoopEntry = (1UL << 0),//值是1,即將進入RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1),//值是2,即將處理Timer事件
    kCFRunLoopBeforeSources = (1UL << 2),//值是4,即將處理source事件
    kCFRunLoopBeforeWaiting = (1UL << 5),//值是32,即將進入休眠狀態(tài)
    kCFRunLoopAfterWaiting = (1UL << 6),//值是64,從休眠中喚醒
    kCFRunLoopExit = (1UL << 7),//值是128,即將退出RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU//所有狀態(tài)
}

總結(jié)RunLoop

  • 官方給出的RunLoop運行邏輯:
4.png
  • 網(wǎng)友自行整理的RunLoop運行邏輯:
2.png

RunLoop實踐

  • 模擬場景:假如我們想創(chuàng)建一個線程,讓他處理完事情之后一直不死,然后去監(jiān)聽我們需要監(jiān)聽的事件,例如(我們會在退出程序到后臺,繼續(xù)監(jiān)聽某些事件的觸發(fā),但是又不想在主線程),

  • 第一步:這時有些開發(fā)者會想到,我們可以直接搞一個屬性來強引用這個線程,如下

/** 線程對象 */
@property (nonatomic, strong) NSThread *thread;
- (void)viewDidLoad {
    [super viewDidLoad];
    //創(chuàng)建線程,讓他執(zhí)行run方法
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread start];
}
- (void)run
{
    //這個方法會走
    NSLog(@"----------run----%@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //執(zhí)行完run方法之后我們在觸摸方法中,把test方法丟給我們創(chuàng)建的線程
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)test
{
    //這時我們發(fā)現(xiàn)這個方法根本沒有打印,因為線程執(zhí)行完run方法就已經(jīng)死了,即使有強引用系統(tǒng)也不允許,我們也可以搞一個類繼承NSThread,重寫dealloc去看看線程到底有沒有銷毀,
    NSLog(@"----------test----%@", [NSThread currentThread]);
}
  • 第二步:既然我們創(chuàng)建的線程死了,那我們就可以不讓他死,然后我們修改了代碼
- (void)run
{
    //在上面的基礎(chǔ)上,我們在run這個方法中這樣寫
    NSLog(@"----------run----%@", [NSThread currentThread]);
    //搞一個死循環(huán),一直死不了
    while (1);
    //但是我們發(fā)現(xiàn)這行打印的代碼一直沒有執(zhí)行,線程已經(jīng)卡死在上面的while循環(huán)了,
    NSLog(@"---------");
}
  • 第三步:這時我們想到了主線程,我們可以模仿主線程的設(shè)計思路去創(chuàng)建出一個RunLoop來完成我們的任務
/** 線程對象 */
@property (nonatomic, strong) NSThread *thread;
- (void)viewDidLoad {
    [super viewDidLoad];
    //創(chuàng)建線程,讓他執(zhí)行run方法
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread start];
}
- (void)run
{
    //這里我們一定要添加一個source或者timer,如果不添加RunLoop跑完一圈就沒有任何可以讓他來監(jiān)聽的對象了,所以就會死掉,如果有source或者timer,就有這樣的機會讓RunLoop監(jiān)聽,有source或者timer的時候卻沒有事件的觸發(fā),RunLoop就在休息狀態(tài),所以有source或者timer不代表RunLoop一定在運行,但是沒有RunLoop一定會死
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    //RunLoop一直在跑,并且監(jiān)聽當前線程的各種事件
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"----------run----%@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //執(zhí)行完run方法之后我們在觸摸方法中,把test方法丟給我們創(chuàng)建的線程
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)test
{
    //所以這個時候我們點擊屏幕就會來到這個方法,點一次觸發(fā)一次,一直在監(jiān)聽,不點擊的時候RunLoop在休息
    NSLog(@"----------test----%@", [NSThread currentThread]);
}
  • 第四步:有的開發(fā)者這樣修改了代碼
- (void)run
{
    //不去創(chuàng)建source,搞一個死循環(huán)讓RunLoop強制不死,就會去監(jiān)聽,
    NSLog(@"----------run----%@", [NSThread currentThread]);
    while (1) {
        [[NSRunLoop currentRunLoop] run];
    }
    NSLog(@"---------");
    //測試結(jié)果發(fā)現(xiàn)這樣確實和上面的做法得到的效果一樣
    //注意:我們來分析以下兩個方法的本質(zhì)區(qū)別,這個做法是強制讓RunLoop不死,一直在跑,及時沒有任何事件觸發(fā),RunLoop也沒有休息,所以這種做法比較耗客戶端的性能,而步驟三中的做法如果沒有任何事件觸發(fā),RunLoop處于休息狀態(tài),所以更好
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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