RunLoop入門 看我就夠了

前言

剛剛聽到RunLoop的時候我也是一臉懵逼,這是什么,有什么用呢,逼格貌似還挺高。然后就開始嘗試去搞懂它,去找博客,但是幾乎所有的博客都是枯燥乏味的,都是講概念,然后給個實例,對于我這個小白來說,根本看不懂好嗎!!
下面放幾個比較好的講解RunLoop的博客
http://www.cocoachina.com/ios/20150601/11970.html
http://www.lxweimin.com/p/b9426458fcf6
下面開始講講我對RunLoop認識。

正文

一、簡介

首先,先象征性的講下RunLoop的概念
從字面上看,就可以看出就是兜圈圈,就是一個死循環嘛。

二、作用

1.保持程序運行
2.處理app的各種事件(比如觸摸,定時器等等)
3.節省CPU資源,提高性能。

三、枯燥知識

下面是關于RunLoop的一些使用簡述。也許有點枯燥,但是也是必須要知道的!(敲黑板ing),我盡量說的通俗易懂一點。

1.兩個API

首先要知道iOS里面有兩套API可以訪問和使用RunLoop:

Foundation

NSRunLoop

Core Foundation

CFRunLoopRef

上面兩套都可以使用,但是要知道CFRunLoopRef是用c語言寫的,是開源的,相比于NSRunLoop更加底層,而NSRunLoop其實是對CFRunLoopRef的一個簡單的封裝。便于使用而已。這樣說來,顯然CFRunLoopRef的性能要高一點。

2.RunLoop與線程

1.每條線程都有唯一的與之對應的RunLoop對象。
2.主線程的RunLoop已經創建好了,而子線程的需要手動創建。(也就是說子線程的RunLoop默認是關閉的,因為有時候開了個線程但卻沒有必要開一個RunLoop,不然反而浪費了資源。 )
3.RunLoop在第一次獲取時創建,在線程結束時銷毀。(這就相當于 線程是一個類,RunLoop是類里的實例變量,這樣便于理解)

3.獲取RunLoop對象

Foundation

[NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象

Core Foundation

CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象 

4.RunLoop相關類

在Core Foundation中有RunLoop的五個類

    CFRunLoopRef
    CFRunLoopModeRef
    CFRunLoopSourceRef
    CFRunLoopTimerRef
    CFRunLoopObserverRef

這五個類的關系如下


CFRunLoopModeRef.png

由圖中可以得出以下幾點:
1.CFRunLoopModeRef代表的是RunLoop的運行模式。
2.一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer。
3.每次調用 RunLoop 的主函數時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode。
4.如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。

CFRunLoopModeRef

系統默認注冊了5個mode

    kCFRunLoopDefaultMode //App的默認Mode,通常主線程是在這個Mode下運行
    UITrackingRunLoopMode //界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
    UIInitializationRunLoopMode // 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
    GSEventReceiveRunLoopMode // 接受系統事件的內部 Mode,通常用不到
    kCFRunLoopCommonModes //這是一個占位用的Mode,不是一種真正的Mode

至于CFRunLoopModeRef的使用我會在 下面的實驗三timer的使用中 詳細說到。

CFRunLoopSourseRef

CFRunLoopSourseRef是事件源,分為兩種

sourse0:非基于port的 (port相當于是系統)

自己寫的方法,響應

sourse1:基于port的

系統提供的

CFRunLoopObserverRef

CFRunLoopObserver是觀察者,可以監聽runLoop的狀態改變
監聽的狀態如下:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { 
kCFRunLoopEntry = (1UL << 0), //即將進入Runloop
kCFRunLoopBeforeTimers = (1UL << 1), //即將處理NSTimer 
kCFRunLoopBeforeSources = (1UL << 2), //即將處理Sources 
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進入休眠 
kCFRunLoopAfterWaiting = (1UL << 6), //剛從休眠中喚醒 
kCFRunLoopExit = (1UL << 7), //即將退出runloop 
kCFRunLoopAllActivities = 0x0FFFFFFFU //所有狀態改變};

四、實驗講解

一、main函數的實驗

先看幾行我們很熟悉的代碼

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

這個是main函數里面的代碼,當app打開后就進入這里。main函數返回類型是int,也就是返回的是

UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

不知道你有沒有想過,如果返回的是0或者1等等,會有什么結果呢,我們來試試。
首先我們先在vc中加一個按鈕,然后添加一個相應,如圖所示:

RunLoopTest1.png

可以想到跑起來的結果:

RunLoopTest2.png

注意看Xcode左上角的按鈕


RunLoopTest3.png

始終處于運行狀態,只有當我們點擊停止才會停止程序。但是當我們改變代碼后

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return 0;
    }
}

再次run起來,發現如下結果:


RunLoopTest4.png

其實可以猜到這樣的結果,因為程序運行到return 0;這一行后就退出了,AppDeleage里面的所有方法都沒有進去。
再來做個試驗:在main里面輸入以下代碼

int res = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"-----");
return res;

你猜會輸出 “-----” 嗎?答案是否定的,你會發現程序始終不會到NSLog(@"-----");這一行來。這就說明了程序一直在運行著。其實這都是RunLoop的功勞,它的其中一個功能就是保持程序的持續運行。有了RunLoop,main里面相當于是這樣的代碼(偽代碼):

BOOL running = YES;
do {
   // 執行各種操作
} while (running);
return 0;

程序是始終在while里面的,是一個死循環。
說到這里你肯定又會疑惑,RunLoop是什么時候創建的。其實在UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))這個函數的內部就已經啟動了一個RunLoop,所以函數一直沒有返回,這才使得程序保持運行。
(注意:這個默認啟動的RunLoop是和主線程相關的!!!)

二、嘗試打印RunLoop對象

既然在理論區學會了如何獲得RunLoop對象,那么我們就打印看看到底是什么玩意。
在按鈕的響應區添加一行輸出:

- (IBAction)ButtonDidClick:(id)sender {
    NSLog(@"ButtonDidClick");
    NSLog(@"----%@", [NSRunLoop currentRunLoop]);
}

結果你會得到下圖的輸出:

RunLoopTest5.png

這張截圖只是輸出的一部分,看不懂沒關系,只要先看一下我圈出來的這幾個名詞,這在后面會有講到。

三、NSTimer的使用

在項目中用的NSTimer其實也和RunLoop有關系,下面我們來做個實驗

實驗一 scheduledTimer方法

修改一下button的響應以及timerTest方法,代碼如下

- (IBAction)ButtonDidClick:(id)sender {
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}

- (void)timerTest
{
    NSLog(@"timerTest----");
}

點擊button可以看到輸出臺每隔一秒鐘就打印"timerTest----"。

實驗二 timerWithTime方法

代碼如下:

- (IBAction)ButtonDidClick:(id)sender {
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}

- (void)timerTest
{
    NSLog(@"timerTest----");
}

但是實驗結果是,點擊button后沒有反應。為什么呢?
噢~原來是少加了一句話,添加后的代碼如下:

- (IBAction)ButtonDidClick:(id)sender {
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}

- (void)timerTest
{
    NSLog(@"timerTest----");
}

可是,為什么實驗二比實驗一要多加一句話呢?解:那是因為scheduledTimer方法會自動添加到當前的runloop里面去,而且runloop的運行模式kCFRunLoopDefaultMode,也就是說實驗一已經將timer自動加入到了一個運行模式為kCFRunLoopDefaultMode的runloop中。

實驗三 有scrollView的情況下使用Timer

首先,按鈕響應以及timerTest的方法如下:

- (IBAction)ButtonDidClick:(id)sender {
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}

- (void)timerTest
{
    NSLog(@"timerTest----");
}

然后在vc中加一個textView,run起來,模擬器界面如下:

RunLoopTest6.png

然后點擊按鈕,隨后滑動textView,可以看到打印結果:

RunLoopTest7.png

沒錯,我在18:22:55秒鐘滑動了textView,然后發現滑動的時候是不打印的,奇怪吧。其實說到底還是RunLoop搞的鬼。可以看到,我們把timer加到了NSDefaultRunLoopMode的runLoop中,而在滑動textview的時候,RunLoop就切換到UITrackingRunLoopMode模式,而上面有提到說:在每次調用 RunLoop 的主函數時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode。 所以定時器就不起作用了。
現在可以思考一下解決方法了!(敲黑板ing)
提示一下,問題出在了模式上面,是不是修改一下模式就好了呢。

解決方法:
上面有提到過五個mode

    kCFRunLoopDefaultMode //App的默認Mode,通常主線程是在這個Mode下運行
    UITrackingRunLoopMode //界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
    UIInitializationRunLoopMode // 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
    GSEventReceiveRunLoopMode // 接受系統事件的內部 Mode,通常用不到
    kCFRunLoopCommonModes //這是一個占位用的Mode,不是一種真正的Mode

其實如果把mode改為kCFRunLoopCommonModes的話就可以既支持kCFRunLoopDefaultMode又支持UITrackingRunLoopMode了。
修改如下:
修改mode類型

- (IBAction)ButtonDidClick:(id)sender {
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

然后run發現就算滾動textView也不會影響打印。有圖有真相:

RunLoopTest8.png

四、有關CFRunLoopSourseRef的實驗

我們在button的響應注釋,然后打個斷點,run后點擊button會發現如下:


RunLoopTest9.png
RunLoopTest10.png

說明了button的點擊是屬于sourse0的。

五、有關CFRunLoopObserverRef的實驗

首先回顧CFRunLoopObserverRef,是RunLoop的監聽者,監聽的狀態如下:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { 
kCFRunLoopEntry = (1UL << 0), //即將進入Runloop
kCFRunLoopBeforeTimers = (1UL << 1), //即將處理NSTimer 
kCFRunLoopBeforeSources = (1UL << 2), //即將處理Sources 
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進入休眠 
kCFRunLoopAfterWaiting = (1UL << 6), //剛從休眠中喚醒 
kCFRunLoopExit = (1UL << 7), //即將退出runloop 
kCFRunLoopAllActivities = 0x0FFFFFFFU //所有狀態改變};

CF里面添加監聽者的方法為

CFRunLoopAddObserver(<#CFRunLoopRef rl#>, <#CFRunLoopObserverRef observer#>, <#CFStringRef mode#>)

第一個參數:RunLoop
第二個參數:observer
第三個參數:mode

方法介紹完,開始敲代碼,vc代碼如下

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self createObserver];
}


- (void)createObserver
{
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"--------%zd", activity);
    });
    
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);  // 添加監聽者,關鍵!
    CFRelease(observer); // 釋放
}

run!
可以看到打印結果:

RunLoopTest11.png

這就是監聽的作用,可以知道當前的RunLoop狀態。
下圖是RunLoop的內部邏輯已經文字描述:(圖片來自網絡)

RunLoop內部邏輯.png
RunLoop的邏輯表述.png

五、最后

其實上述只是runloop的一個入門而已,想要深入其中,還是需要在項目中嘗試使用它。
最后的最后,這篇文章的發表經歷了整整三天的時間,總結,實驗,截圖,碼字。。。總之,如果你覺得對你有所幫助,就請給我點個贊唄~
最后的最后的最后,我的水平有限,肯定有很多錯誤和不足之處,請各位不吝賜教,謝謝。

六、更新

也許你會覺得RunLoop的五個相關類會有點亂,下面我再來理一下。

    CFRunLoopRef
    CFRunLoopModeRef
    CFRunLoopSourceRef
    CFRunLoopTimerRef
    CFRunLoopObserverRef

我的理解是,CFRunLoopRef就是RunLoop,而SourceRef、TimerRef、ObserverRef是CFRunLoopRef的內容,而ModeRef指的是mode的屬性。
先放個代碼:

[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];

這句話的意思就是:給當前的RunLoop添加一個內容為Sourse的而且屬性是NSDefaultRunLoopMode的mode。是不是一下子清楚了~如果還不懂,那就再舉個栗子:

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

這句話的意思是:給當前的RunLoop添加一個內容為timer的而且屬性是NSRunLoopCommonModes的mode。
現在是不是懂了呢~

看完入門,看實戰唄~
傳送門,RunLoop已入門?不來應用一下?:http://www.lxweimin.com/p/c0a550d2ac97

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容