RunLoop

RunLoop

RunLoop概述

什么是RunLoop

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

RunLoop對(duì)象

  • 在ios中可以通過(guò)兩個(gè)api來(lái)訪問(wèn)RunLoop
    • NSRunLoop(Foundation框架下)和CFRunLoopRef(Core Foundation框架下)
    • NSRunLoop是oc層面的,是對(duì)CFRunLoopRef進(jìn)行了包裝,網(wǎng)上會(huì)有CFRunLoopRef的源碼,下載地址: http://opensource.apple.com/source/CF/CF-1151.16/
  • RunLoop與線程的關(guān)系:每一個(gè)線程都會(huì)有唯一一個(gè)RunLoop與之對(duì)應(yīng),實(shí)際內(nèi)部是以字典的形式管理,主線程的RunLoop已經(jīng)自己創(chuàng)建好了,子線程的需要手動(dòng)創(chuàng)建,創(chuàng)建好的RunLoop會(huì)一直存在,直到對(duì)應(yīng)線程結(jié)束才會(huì)銷(xiāo)毀
    //獲得當(dāng)前線程的RunLoop對(duì)象
    [NSRunLoop currentRunLoop];
    //獲取主線程的RunLoop對(duì)象
    [NSRunLoop mainRunLoop];

RunLoop的五個(gè)類(lèi)

Core Foundation中關(guān)于RunLoop的5個(gè)類(lèi)

  • CFRunLoopRef(RunLoop類(lèi))
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoopModeRef

  • CFRunLoopModeRef代表RunLoop的運(yùn)行模式,一個(gè)RunLoop可以有許多個(gè)mode,每一個(gè)mode又可以有許多個(gè)Source/Timer/Observer
  • 每次RunLoop啟動(dòng)時(shí)将宪,只能指定其中一個(gè) Mode,這個(gè)Mode被稱(chēng)作 CurrentMode
  • 如果需要切換Mode,只能退出Loop啊犬,再重新指定一個(gè)Mode進(jìn)入
  • 這樣做主要是為了分隔開(kāi)不同組的Source/Timer/Observer切蟋,讓其互不影響
  • 系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:
    • kCFRunLoopDefaultMode:App的默認(rèn)Mode愉豺,通常主線程是在這個(gè)Mode下運(yùn)行
    • UITrackingRunLoopMode:界面跟蹤 Mode阔蛉,用于 ScrollView 追蹤觸摸滑動(dòng)阵幸,保證界面滑動(dòng)時(shí)不受其他 Mode 影響
    • UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode瓣喊,啟動(dòng)完成后就不再使用
    • GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode坡慌,通常用不到
    • kCFRunLoopCommonModes: 這是一個(gè)占位用的Mode,不是一種真正的Mode
//schedule方法會(huì)默認(rèn)把timer的事件加入RunLoop中,timerWith的方法不會(huì)
    //RunLoop在運(yùn)行中只會(huì)指定一個(gè)mode,若果切換模式要停止RunLoop,并重新開(kāi)始新的模式下的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];
    // 定時(shí)器默認(rèn)只運(yùn)行在NSDefaultRunLoopMode下藻三,一旦RunLoop進(jìn)入其他模式洪橘,這個(gè)定時(shí)器就不會(huì)工作
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    // 定時(shí)器會(huì)跑在標(biāo)記為common modes的模式下
    // 標(biāo)記為common modes的模式:UITrackingRunLoopMode和kCFRunLoopDefaultMode
    //如果mode設(shè)置為common,那么表示定時(shí)器在這兩個(gè)mode下都可以跑
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    - (void)run
{
    NSLog(@"----run");
}

CFRunLoopSourceRef

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

CFRunLoopObserverRef

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

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

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

總結(jié)RunLoop

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

RunLoop實(shí)踐

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

  • 第一步:這時(shí)有些開(kāi)發(fā)者會(huì)想到,我們可以直接搞一個(gè)屬性來(lái)強(qiáng)引用這個(gè)線程,如下

/** 線程對(duì)象 */
@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
{
    //這個(gè)方法會(huì)走
    NSLog(@"----------run----%@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //執(zhí)行完run方法之后我們?cè)谟|摸方法中,把test方法丟給我們創(chuàng)建的線程
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)test
{
    //這時(shí)我們發(fā)現(xiàn)這個(gè)方法根本沒(méi)有打印,因?yàn)榫€程執(zhí)行完run方法就已經(jīng)死了,即使有強(qiáng)引用系統(tǒng)也不允許,我們也可以搞一個(gè)類(lèi)繼承NSThread,重寫(xiě)dealloc去看看線程到底有沒(méi)有銷(xiāo)毀,
    NSLog(@"----------test----%@", [NSThread currentThread]);
}
  • 第二步:既然我們創(chuàng)建的線程死了,那我們就可以不讓他死,然后我們修改了代碼
- (void)run
{
    //在上面的基礎(chǔ)上,我們?cè)趓un這個(gè)方法中這樣寫(xiě)
    NSLog(@"----------run----%@", [NSThread currentThread]);
    //搞一個(gè)死循環(huán),一直死不了
    while (1);
    //但是我們發(fā)現(xiàn)這行打印的代碼一直沒(méi)有執(zhí)行,線程已經(jīng)卡死在上面的while循環(huán)了,
    NSLog(@"---------");
}
  • 第三步:這時(shí)我們想到了主線程,我們可以模仿主線程的設(shè)計(jì)思路去創(chuàng)建出一個(gè)RunLoop來(lái)完成我們的任務(wù)
/** 線程對(duì)象 */
@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
{
    //這里我們一定要添加一個(gè)source或者timer,如果不添加RunLoop跑完一圈就沒(méi)有任何可以讓他來(lái)監(jiān)聽(tīng)的對(duì)象了,所以就會(huì)死掉,如果有source或者timer,就有這樣的機(jī)會(huì)讓RunLoop監(jiān)聽(tīng),有source或者timer的時(shí)候卻沒(méi)有事件的觸發(fā),RunLoop就在休息狀態(tài),所以有source或者timer不代表RunLoop一定在運(yùn)行,但是沒(méi)有RunLoop一定會(huì)死
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    //RunLoop一直在跑,并且監(jiān)聽(tīng)當(dāng)前線程的各種事件
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"----------run----%@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //執(zhí)行完run方法之后我們?cè)谟|摸方法中,把test方法丟給我們創(chuàng)建的線程
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)test
{
    //所以這個(gè)時(shí)候我們點(diǎn)擊屏幕就會(huì)來(lái)到這個(gè)方法,點(diǎn)一次觸發(fā)一次,一直在監(jiān)聽(tīng),不點(diǎn)擊的時(shí)候RunLoop在休息
    NSLog(@"----------test----%@", [NSThread currentThread]);
}
  • 第四步:有的開(kāi)發(fā)者這樣修改了代碼
- (void)run
{
    //不去創(chuàng)建source,搞一個(gè)死循環(huán)讓RunLoop強(qiáng)制不死,就會(huì)去監(jiān)聽(tīng),
    NSLog(@"----------run----%@", [NSThread currentThread]);
    while (1) {
        [[NSRunLoop currentRunLoop] run];
    }
    NSLog(@"---------");
    //測(cè)試結(jié)果發(fā)現(xiàn)這樣確實(shí)和上面的做法得到的效果一樣
    //注意:我們來(lái)分析以下兩個(gè)方法的本質(zhì)區(qū)別,這個(gè)做法是強(qiáng)制讓RunLoop不死,一直在跑,及時(shí)沒(méi)有任何事件觸發(fā),RunLoop也沒(méi)有休息,所以這種做法比較耗客戶(hù)端的性能,而步驟三中的做法如果沒(méi)有任何事件觸發(fā),RunLoop處于休息狀態(tài),所以更好
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市棵帽,隨后出現(xiàn)的幾起案子熄求,更是在濱河造成了極大的恐慌,老刑警劉巖逗概,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抡四,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡仗谆,警方通過(guò)查閱死者的電腦和手機(jī)指巡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)隶垮,“玉大人藻雪,你說(shuō)我怎么就攤上這事±晖蹋” “怎么了勉耀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蹋偏。 經(jīng)常有香客問(wèn)我便斥,道長(zhǎng),這世上最難降的妖魔是什么威始? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任枢纠,我火速辦了婚禮,結(jié)果婚禮上黎棠,老公的妹妹穿的比我還像新娘晋渺。我一直安慰自己,他們只是感情好脓斩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布木西。 她就那樣靜靜地躺著,像睡著了一般随静。 火紅的嫁衣襯著肌膚如雪八千。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音恋捆,去河邊找鬼照皆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鸠信,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播论寨,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼星立,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了葬凳?” 一聲冷哼從身側(cè)響起绰垂,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎火焰,沒(méi)想到半個(gè)月后劲装,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昌简,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年占业,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纯赎。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谦疾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出犬金,到底是詐尸還是另有隱情念恍,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布晚顷,位于F島的核電站峰伙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏该默。R本人自食惡果不足惜瞳氓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望栓袖。 院中可真熱鬧顿膨,春花似錦、人聲如沸叽赊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)必指。三九已至囊咏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梅割。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工霜第, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人户辞。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓泌类,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親底燎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刃榨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容