前言
剛剛聽到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
這五個類的關系如下
由圖中可以得出以下幾點:
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中加一個按鈕,然后添加一個相應,如圖所示:
可以想到跑起來的結果:
注意看Xcode左上角的按鈕
始終處于運行狀態,只有當我們點擊停止才會停止程序。但是當我們改變代碼后
int main(int argc, char * argv[]) {
@autoreleasepool {
return 0;
}
}
再次run起來,發現如下結果:
其實可以猜到這樣的結果,因為程序運行到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]);
}
結果你會得到下圖的輸出:
這張截圖只是輸出的一部分,看不懂沒關系,只要先看一下我圈出來的這幾個名詞,這在后面會有講到。
三、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起來,模擬器界面如下:
然后點擊按鈕,隨后滑動textView,可以看到打印結果:
沒錯,我在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也不會影響打印。有圖有真相:
四、有關CFRunLoopSourseRef的實驗
我們在button的響應注釋,然后打個斷點,run后點擊button會發現如下:
說明了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!
可以看到打印結果:
這就是監聽的作用,可以知道當前的RunLoop狀態。
下圖是RunLoop的內部邏輯已經文字描述:(圖片來自網絡)
五、最后
其實上述只是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