RunLoop的基本了解
**1 . RunLoop字面的意思 : **運行循環 / 跑圈
**2 . 基本作用 : **
1.保證程序的持續運行(這就是為什么ios程序能一直活著不會死)
2.處理app中的各種事件(比如觸摸事件、定時器事件(NSTimer)、selector事件(performSelector)
3.節省CPU資源,提高程序性能,有事情就做事情,沒事情就休息-
3 . 重要說明
(1)如果沒有Runloop,那么程序一啟動就會退出,什么事情都做不了;如下圖,當程序執行完第2行,在執行到低3行后就結束程序.
沒有RunLoop的程序.png(2)如果有了Runloop,那么相當于在內部有一個死循環,能夠保證程序的持續運行;如下圖,main函數內有了一個RunLoop(由do-while循環模擬),程序會持續的運行,不會馬上就退出.
有RunLoop的程序.png(3)main函數中的Runloop
main函數中的RunLoop.png
a. 在UIApplication函數內部就啟動了一個Runloop,所以UIApplicationMain函數一直沒有返回,保持了程序的還需運行,該函數返回一個int類型的值
b. 這個默認啟動的Runloop是跟主線程相關聯的 -
RunLoop與線程
- 1.Runloop和線程的關系:每條線程,都有唯一的一個RunLoop對象與之相對應
問題:如何讓子線程不死
回答:給這條子線程開啟一個Runloop - 2.Runloop的創建:
1.主線程Runloop已經創建好了
2.子線程的runloop需要手動創建 - 3.Runloop的生命周期:在第一次獲取時創建,在線程結束時銷毀
- 1.Runloop和線程的關系:每條線程,都有唯一的一個RunLoop對象與之相對應
RunLoop對象
1.在iOS開發中有兩套api來訪問Runloop
a.Foundation 框架 以NS開頭 - NSRunloop
b.Core Foundation 框架 以CF開頭 - CFRunloopRef
2.NSRunLoop和CFRunLoopRef都代表著RunLoop對象,它們是等價的,可以互相轉換
3.NSRunLoop是基于CFRunLoopRef的一層OC包裝,更加的面向對象,所以要了解RunLoop內部結構,需要多研究CFRunLoopRef層面的API(Core Foundation層面)-
獲取RubLoop對象
- 獲取Foundation框架下的RunLoop對象
/*****通過Foundation框架獲取*****/
//獲取當前runLoop
NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
//獲取主runloop
NSRunLoop * mainRunLoop = [NSRunLoop mainRunLoop];
NSLog(@"%p-------%p",runLoop,mainRunLoop);
- 獲取Core Foundation框架下的RunLoop對象
//*****通過Core Foundation框架獲取*****//
//獲取當前的runloop
CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
//獲取主runLoop
CFRunLoopRef cfMainRunLoop = CFRunLoopGetMain();
NSLog(@"%p-------%p",cfRunLoop,cfMainRunLoop);
- Foundation框架的RunLoop對象轉換成Core Foundation框架的RunLoop對象
**注意 : **雖然說Foundation框架下的和Core Foundation框架下的RunLoop對象是相同的,但是只有通過轉換后的地址才是相同的
CFRunLoopRef changeRunLoop = mainRunLoop.getCFRunLoop;
- RunLoop與子線程之間的關系
1)子線程與RunLoop之間是一一對應的
2)主線程對應的runloop默認已經開啟了,子線程對應的runloop需要主動創建
3)如何創建子線程對應的runloop :[NSRunLoop currentRunLoop](該方法本身是懶加載的,第一次調用該方法的時候如果發現runloop不存在那么會直接創建一個)
4)runloop是需要開啟
/***runLoop和線程之間的關系***/
//創建一個子線程
[NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];
/
-(void)task
{
NSLog(@"執行了task");
//下一行代碼內,已經創建了子線程對應的runLoop,但是子線程的RunLoop需要手動開啟,開啟的方法是通過子線程的RunLoop調用run方法
NSLog(@"%p---------%p",[NSRunLoop currentRunLoop],[NSRunLoop mainRunLoop]);
//雖然已經開啟了子線程的RunLoop,但是此處子線程的RunLoop并沒有運行起來,因為在RunLoop內,至少要有一個源或者Timer,這樣runLoop才不會在剛創建就結束運行
[[NSRunLoop currentRunLoop] run];
NSLog(@"------------%@",[NSThread currentThread]);
}
如何持續運行子線程內的RunLoop:
1.子線程內的RunLoop需要手動開啟,通過子線程對象,調用run方法"[[NSRunLoop currentRunLoop] run];"
2.在子線程內的RunLoop內,至少要有一個源(事件源)或者Timer,這樣才能包裝RunLoop不會再剛創建就結束運行
**注意點 : **
1.開一個子線程創建runloop,不是通過alloc init方法創建,而是直接通過調用currentRunLoop方法來創建,它本身是一個懶加載的。
2.在子線程中,如果不主動獲取Runloop的話,那么子線程內部是不會創建Runloop的。可以下載CFRunloopRef的源碼,搜索_CFRunloopGet0,查看代碼。
3.Runloop對象是利用字典來進行存儲,而且key是對應的線程,Value為該線程對應的Runloop。
RunLoop的相關類
-
RunLoop一共有五個與之相關的類
a.CFRunloopRef
b.CFRunloopModeRef【Runloop的運行模式】
c.CFRunloopSourceRef【Runloop要處理的事件源】
d.CFRunloopTimerRef【Timer事件】
e.CFRunloopObserverRef【Runloop的觀察者(監聽者)】-
CFRunloopModeRef - RunLoop的運行模式
Runloop要想跑起來:
1.它的內部必須要有一個mode,
2.這個mode里面必須有source\timer(事件),至少要有其中的一個(事件),如果僅僅是有observer,那么RunLoop也會立即退出。
01.CFRunloopModeRef代表著Runloop的運行模式
02.一個Runloop中可以有多個mode,一個mode里面又可以有多個source\observer\timer等等
03.每次runloop啟動的時候,只能指定一個mode,這個mode被稱為該Runloop的當前mode
04.如果需要切換mode,只能先退出當前Runloop,再重新指定一個mode進入
05.這樣做主要是為了分割不同組的定時器等,讓他們相互之間不受影響
06.系統默認注冊了5個mode
a.kCFRunLoopDefaultMode(C語言寫法):App的默認Mode,通常主線程是在這個Mode下運行
NSDefaultRunLoopMode (OC語言寫法)
b.UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
c.UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
d.GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到
e.kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode,不過這個Model一般等同于同時擁有默認的Model和界面跟蹤Model
即kCFRunLoopCommonModes = kCFRunLoopDefaultMode + UITrackingRunLoopMode - RunLoop與相關類之間的關系
-
CFRunloopModeRef - RunLoop的運行模式
-
CFRunloopTimerRef
RunLoop中的定時器,基本等同于OC中的定時器,在RunLoop中的定時器,一般指的是NSTimer定時器和GCD定時器兩種.-
NSTimer定時器
說明:
1. runloop一啟動就會選中一種模式,當選中了一種模式之后其它的模式就都不鳥。
2. 一個mode里面可以添加多個NSTimer,也就是說以后當創建NSTimer的時候,可以指定它是在什么模式下運行的。
3. 它是基于時間的觸發器,說直白點那就是時間到了我就觸發一個事件,觸發一個操作?;旧险f的就是NSTimer
創建NSTimer定時器的方法
注意 : 如果RunLoop的運行模式改變,那么在當前模式下的定時器就會停止工作
-
NSTimer定時器
//方法一
scheduledTimerWithTimeInterval:
方法的說明 : 創建定時器并默認添加到當前線程的Runloop中,
并指定運行模式為默認的運行模式(kCFRunLoopDefaultMode)
示例:
-(void)timer2
{
//NSTimer 調用了scheduledTimer方法,那么會自動添加到當前的runloop里面去,而且runloop的運行模式kCFRunLoopDefaultMode
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
}
/**************愉快的分割線***************/
//方法二
timerWithTimeInterval:
方法說明 : 通過這個方法創建的定時器,
如果該定時器要工作還需要添加到runloop中,
并需要明確指定相應的運行模式
-(void)timer1
{
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//定時器添加到UITrackingRunLoopMode模式,一旦runloop切換模式,那么定時器就不工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//定時器添加到NSDefaultRunLoopMode模式,一旦runloop切換模式,那么定時器就不工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//占位模式:common modes標記
//被標記為common modes的模式 等同于同時可以在kCFRunLoopDefaultMode + UITrackingRunLoopMode下運行
//在這個模式下,RunLoop既可以運行在Default模式下,也可以運行在Tracking模式下,
//在這兩種模式來回切換時,定時器都不會停止工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- **GCD定時器**
**GCD定時器的特點 : **GCD的定時器不會受到Runloop運行模式的影響,可以控制任務在主線程還是子線程執行
**注意點 : **
1. 需要添加強引用,否則在創建GCD定時器的代碼塊結束后,定時器就會被釋放掉
2. 注意隊列里面不要添加了其它的任務
**創建GCD定時器方法介紹**
1.獲取隊列(不再贅述)
2.創建一個GCD定時器
參數解釋:
第一個參數:DISPATCH_SOURCE_TYPE_TIMER 定時器
第二個參數:描述信息
第三個參數:總是穿0
第四個參數:隊列 (決定GCD要執行的任務在哪個線程中調用的 并發隊列--子線程中調用)
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
3.設置定時器的開始時間,間隔時間以及精確度
//設置開始時間
參數解釋:
第一個參數:從什么時間開始
第二個參數:多長時間后開始調用
dispatch_time_t start =dispatch_time(DISPATCH_TIME_NOW,3.0 *NSEC_PER_SEC);
//設置間隔時間
uint64_t intevel = 1.0 * NSEC_PER_SEC;
//設置定時器以及精確度
參數解釋:
第一個參數:定時器對象
第二個參數:從什么時候開始計時 DISPATCH_TIME_NOW 現在
第三個參數:間隔時間 2.0 ns
第四個參數:精準度 允許的誤差 絕對精準~0
dispatch_source_set_timer(timer, start, intevel, 0 * NSEC_PER_SEC);
4.設置定時器開始后回調的方法
dispatch_source_set_event_handler(timer, ^{
要執行回調的代碼
});
5.執行定時器
dispatch_resume(timer);
**創建GCD定時器 示例**
//0.創建一個隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//1.創建一個GCD的定時器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//2.設置定時器的開始時間,間隔時間以及精準度
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW,3.0 *NSEC_PER_SEC);
uint64_t intevel = 1.0 * NSEC_PER_SEC;
dispatch_source_set_timer(timer, start, intevel, 0 * NSEC_PER_SEC);
//3.設置定時器開啟后回調的方法
dispatch_source_set_event_handler(timer, ^{
NSLog(@"------%@",[NSThread currentThread]);
});
//4.執行定時器
dispatch_resume(timer);
//強引用
self.timer = timer;
**GCD定時器補充 : **
DISPATCH_SOURCE_TYPE_TIMER 定時響應(定時器事件)
DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信號時響應
DISPATCH_SOURCE_TYPE_READ IO操作,如對文件的操作、socket操作的讀響應
DISPATCH_SOURCE_TYPE_WRITE IO操作,如對文件的操作、socket操作的寫響應
DISPATCH_SOURCE_TYPE_VNODE 文件狀態監聽,文件被刪除、移動、重命名
DISPATCH_SOURCE_TYPE_PROC 進程監聽,如進程的退出、創建一個或更多的子線程、進程收到UNIX信號
下面兩個都屬于Mach相關事件響應
DISPATCH_SOURCE_TYPE_MACH_SEND
DISPATCH_SOURCE_TYPE_MACH_RECV
下面兩個都屬于自定義的事件,并且也是有自己來觸發
DISPATCH_SOURCE_TYPE_DATA_ADD
DISPATCH_SOURCE_TYPE_DATA_OR
- **CFRunloopSourceRef**
(1)是事件源也就是輸入源,有兩種分類模式;
a.一種是按照蘋果官方文檔進行劃分的
b.另一種是基于函數的調用棧來進行劃分的(source0和source1)。
(2)具體的分類情況
a.以前的分法
Port-Based Sources
Custom Input Sources
Cocoa Perform Selector Sources
b.現在的分法
Source0:非基于Port的(用戶主動觸發的事件)
Source1:基于Port的
(3)可以通過打斷點的方式查看一個方法的函數調用棧
- **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 //所有狀態改變
};
- **如何監聽**
//創建一個runloop監聽者
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
//通過switch判斷activity的狀態
switch (activity) {
case kCFRunLoopEntry: NSLog(@"進入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即將處理timer事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"將處理soure事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即將進入休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"被喚醒");
break;
case kCFRunLoopExit:
NSLog(@"runloop退出");
break;
default:
break; }
});
//為runloop添加一個監聽者
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
#Runloop運行邏輯
每次運行RunLoop,線程中的RunLoop都會自動處理之前未處理的消息,并通知相關的觀察者.具體的順序如下:
1. 通知觀察者,RunLoop已經啟動
2.通知觀察者任何即將要開始的定時器
3.通知觀察者任何即將要啟動的非基于端口的源
4.啟動任何準備好的非基于端口的源
5.如果基于端口的源準備好,并處于等待狀態,立即啟動;并進入步驟9
6.通知觀察者線程進入休眠
7.將下城置于休眠,知道下面任意事件的發生:
-某一事件到達基于端口的源
-定時器啟動
-RunLoop設置的時間已經超時
-RunLoop被顯示喚醒
8. 通知觀察者線程將被喚醒
9. 處理未處理的事件
-如果用戶定義的定時器啟動,處理定時器時間,并重啟RunLoop.進入步驟2
-如果輸入源啟動,傳遞相應的消息
-如果RunLoop被顯示喚醒,而且時間還沒到,重啟RunLoop.進入步驟2
10.通知觀察者RunLoop結束

#Runloop應用
1)NSTimer
2)ImageView顯示:控制方法在特定的模式下可用
3)PerformSelector
4)常駐線程:在子線程中開啟一個runloop
子線程的運行循環需要手動創建
子線程的運行循環開啟后如果內部什么都沒有那么會直接退出
添加Port或添加NSTimer, 只是添加Observer無效
子線程的運行循環需要手動開啟
5)自動釋放池
第一次創建:進入runloop的時候
最后一次釋放:runloop退出的時候
其它創建和釋放:當runloop即將休眠的時候會把之前的自動釋放池釋放,然后重新創建一個新的釋放池