RunLoop詳情

RunLoop簡介

運行循環(huán),在程序運行過程中循環(huán)做一些事情,如果沒有Runloop程序執(zhí)行完畢就會立即退出,如果有Runloop程序會一直運行,并且時時刻刻在等待用戶的輸入操作。RunLoop可以在需要的時候自己跑起來運行,在沒有操作的時候就停下來休息。充分節(jié)省CPU資源,提高程序性能。

RunLoop作用

1、保持程序持續(xù)運行
2、處理App中的各種事件
3、節(jié)省CPU資源,提高程序性能

RunLoop和線程間的關系

1、每條線程都有唯一的一個與之對應的RunLoop對象
2、RunLoop保存在一個全局的Dictionary里,線程作為key,RunLoop作為value
3、主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建
4、RunLoop在第一次獲取時創(chuàng)建,在線程結束時銷毀

RunLoop的結構

主要的成員變量:
CFMutableSetRef _sources0;//基于Port的線程間通信
CFMutableSetRef _sources1;//觸摸事件,PerformSelectors
CFMutableArrayRef _observers;//定時器,NSTimer
CFMutableArrayRef _timers;//監(jiān)聽器,用于監(jiān)聽RunLoop的狀態(tài)

一個RunLoop包含若干個Mode,每個Mode又包含若干個Source0/Source1/Timer/Observer

RunLoop相關類及作用

CFRunLoopRef:獲得當前RunLoop和主RunLoop
CFRunLoopModeRef:RunLoop 運行模式,只能選擇一種,在不同模式中做不同的操作
CFRunLoopSourceRef: 事件源,輸入源
CFRunLoopTimerRef:定時器時間
CFRunLoopObserverRef:觀察者

CFRunLoopModeRef的5種模式

1、kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
2、UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
3、UIInitializationRunLoopMode:在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用,會切換到kCFRunLoopDefaultMode
4、GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
5、kCFRunLoopCommonModes:這是一個占位用的Mode,作為標記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一種真正的Mode

CFRunLoopModeRef的應用

解決在ScrollView中,滑動控制器時候定時器失效的方案

/*
創(chuàng)建定時器
*/
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];

/*
把定時器加入到runloop中

*重點是Mode*

1、如果Mode使用NSDefaultRunLoopMode:在ScrollView滑動的時候,定時器就會失效,停止滑動的時候又會恢復
2、如果Mode使用UITrackingRunLoopMode:在ScrollView滑動的時候,定時器是正常的,停止滑動的時候就會失效

原因是:當ScrollView滑動的時候,RunLoop的Mode會自動切換成UITrackingRunLoopMode模式,因此timer失效;當停止滑動,RunLoop又會切換回NSDefaultRunLoopMode模式,因此timer又會重新啟動。

為什么使用NSRunLoopCommonModes模式就可以滑動和不滑動的時候都正常了呢?因為NSRunLoopCommonModes是一個占位用的Mode,用來標記UITrackingRunLoopMode和kCFRunLoopDefaultMode。因此UITrackingRunLoopMode和kCFRunLoopDefaultMode被標記之后的作用就相同了。

*/
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

CFRunLoopObserverRef的應用

CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變

//創(chuàng)建監(jiān)聽者
     /*
     第一個參數(shù) CFAllocatorRef allocator:分配存儲空間 CFAllocatorGetDefault()默認分配
     第二個參數(shù) CFOptionFlags activities:要監(jiān)聽的狀態(tài) kCFRunLoopAllActivities 監(jiān)聽所有狀態(tài)
     第三個參數(shù) Boolean repeats:YES:持續(xù)監(jiān)聽 NO:不持續(xù)
     第四個參數(shù) CFIndex order:優(yōu)先級,一般填0即可
     第五個參數(shù) :回調(diào) 兩個參數(shù)observer:監(jiān)聽者 activity:監(jiān)聽的事件
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop進入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop要處理Timers了");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"RunLoop要處理Sources了");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop要休息了");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"RunLoop醒來了");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop退出了");
                break;
                
            default:
                break;
        }
    });
    
    // 給RunLoop添加監(jiān)聽者
    /*
     第一個參數(shù) CFRunLoopRef rl:要監(jiān)聽哪個RunLoop,這里監(jiān)聽的是主線程的RunLoop
     第二個參數(shù) CFRunLoopObserverRef observer 監(jiān)聽者
     第三個參數(shù) CFStringRef mode 要監(jiān)聽RunLoop在哪種運行模式下的狀態(tài)
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
     /*
     CF的內(nèi)存管理(Core Foundation)
     凡是帶有Create、Copy、Retain等字眼的函數(shù),創(chuàng)建出來的對象,都需要在最后做一次release
     GCD本來在iOS6.0之前也是需要我們釋放的,6.0之后GCD已經(jīng)納入到了ARC中,所以我們不需要管了
     */
    CFRelease(observer);
  • CFOptionFlags activities系統(tǒng)提供的枚舉
kCFRunLoopEntry = (1UL << 0),   //   即將進入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7),// 即將退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU

RunLoop退出

主線程銷毀RunLoop退出
Mode中有一些Timer 、Source、 Observer,這些保證Mode不為空時保證RunLoop沒有空轉(zhuǎn)并且是在運行的,當Mode中為空的時候,RunLoop會立刻退出
我們在啟動RunLoop的時候可以設置什么時候停止

子線程開啟RunLoop

/*
創(chuàng)建子線程并開啟
*/
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(show) object:nil];
[thread start];

/*
在子線程中創(chuàng)建RunLoop
*/
-(void)show{
    // 注意:打印方法一定要在RunLoop創(chuàng)建開始運行之前,如果在RunLoop跑起來之后打印,RunLoop先運行起來,已經(jīng)在跑圈了就出不來了,進入死循環(huán)也就無法執(zhí)行后面的操作了。
    // 但是此時點擊Button還是有操作的,因為Button是在RunLoop跑起來之后加入到子線程的,當Button加入到子線程RunLoop就會跑起來
    NSLog(@"%s",__func__);
    // 1.創(chuàng)建子線程相關的RunLoop,在子線程中創(chuàng)建即可,并且RunLoop中要至少有一個Timer 或 一個Source 保證RunLoop不會因為空轉(zhuǎn)而退出,因此在創(chuàng)建的時候直接加入
    // 添加Source [NSMachPort port] 添加一個端口
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    // 添加一個Timer
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];    
    //創(chuàng)建監(jiān)聽者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop進入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop要處理Timers了");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"RunLoop要處理Sources了");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop要休息了");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"RunLoop醒來了");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop退出了");
                break;
            
            default:
                break;
        }
    });
    // 給RunLoop添加監(jiān)聽者
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    // 2.子線程需要開啟RunLoop
    [[NSRunLoop currentRunLoop]run];
    CFRelease(observer);
}

自動釋放池

RunLoop內(nèi)部有一個自動釋放池,當RunLoop開啟時,就會自動創(chuàng)建一個自動釋放池,當RunLoop在休息之前會釋放掉自動釋放池的東西,然后重新創(chuàng)建一個新的空的自動釋放池,當RunLoop被喚醒重新開始跑圈時,Timer,Source等新的事件就會放到新的自動釋放池中,當RunLoop退出的時候也會被釋放。

重點

創(chuàng)建子線程相關的RunLoop,RunLoop中要至少有一個Timer 或 一個Source 保證RunLoop不會因為空轉(zhuǎn)而退出,因此在創(chuàng)建的時候直接加入,如果沒有加入Timer或者Source,或者只加入一個監(jiān)聽者,運行程序會崩潰

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

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

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