什么是RunLoop?
從字面上來看是運行循環的意思.
內部就是一個do{}while循環,在這個循環里內部不斷的處理各種任務(比如:source/timer/Observer)
RunLoop的存在其實就是為線程而存在的.線程的作用就是執行一個特定的任務,但是默認情況下線程執行完任務后就不能再次執行任務,這是因為默認情況下線程是沒有開啟RunLoop的.如果開啟RunLoop之后,線程執行完任務之后,會一直等待,直到再次接受到任務,接續執行任務.線程銷毀前,會先釋放這個線程所對應的RunLoop.
RunLoop基本作用
保持程序的持續運行,保持線程的持續運行.
處理App中的各種事件(比如觸摸事件,定時器事件,Selector事件)
節省CPU資源,提高程序性能:該做事時做事,該休息時休息
RunLoop對象
ios中有2套API來訪問和使用RunLoop
一套是Fundation(純OC的)框架中的
NSRunLoop
// 獲得當前線程的RunLoop對象[NSRunLoop currentRunLoop];// 獲得主線程的RunLoop對象[NSRunLoop mainRunLoop];
一套是Core Fundation(純C語言的)框架中的
CFRunLoopRef
// 獲得當前線程的RunLoop對象CFRunLoopGetCurrent();// 獲得主線程的RunLoop對象CFRunLoopGetMain();
NSRunLoo和CFRunLoopRef都代表著RunLoop對象.NSRunLoop是基于CFRunLoopRef的一層OC包裝
RunLoop與線程
每條線程都有唯一的一個與之對應的RunLoop對象
主線程的Runloop系統已經自動創建好了,子線程的RunLoop需要手動創建
RunLoop在第一次獲取時由系統自動創建,在線程結束時銷毀
如果想給子線程創建RunLoop,不能直接alloc&init,只要調用獲取當前線程RunLoop方法即可,系統會自動放回當前線程的RunLoop,如果當前線程沒有RunLoop,系統會自動創建.
RunLoop相關類
Core Fundation中關于RunLoop的5個類
CFRunLoopRef: RunLoop對象
CFRunLoopModeRef: RunLoop運行模式.
CFRunLoopSoruceRef: 事件源(輸入源)
CFRunLoopTimerRef:基于時間的觸發器.
CFRunLoopObserverRef: 觀察者,能夠監聽RunLoop的狀態改變
CFRunLoopModeRef
CFRunLoopModeRef代表RunLoop運行模式
一個RunLoop對象包含若干個Mode(模式),每個Mode又包含若干個 source/Timer/Observer
RunLoop運行時,只能指定一個Mode, 這個Mode又稱之為CurrentMode,然后RunLoo就執行CurrentMode中的source/Timer/Observer
如果需要切換Mode,只能退出RunLoop,再重新指定一個Mode進入,這樣做是為了分隔開不同組的Source/Timer/Observer,讓其不受影響
系統默認注冊了5個Mode:
NSDefaultRunLoopMode: App的默認Mode,通常主線程實在這個模式下運行
UITrackingRunLoopMode:界面跟蹤Mode,用于界面控件(ScrollView,tableView等等)追蹤觸摸滑動,保證界面滑動時不受其他Mode影響
UIInitializationRunLoopMode:在剛啟動App是進入的第一個Mode,啟動完成后就不再使用
GSEventReceiveRunLoopMode:接收系統事件的內部Mode,通常用不到
NSRunLoopCommonMode:這是一個占位的Mode,不是一種真正的Mode,(可以看成模式組,默認情況下包括了NSDefaultRunLoopMode,UITrackingRunLoopMode)兩種模式.
CFRunLoopTimerRef
CFRunLoopTimerRef是基于時間的觸發器
CFRunLoopTimerRef基本上說的就是NSTimer,它受RunLoop的Mode影響
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//創建一個NSTimer定時器,默認情況下NSTimer是不會執行的,只有把NSTimer添加到RunLoop中,由RunLoop管理執行
NSTimer * timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(show) userInfo:nil repeats:YES];
// 在當前線程中RunLoop添加一個timer, 并告訴runLoop, 這個timer只能在NSDefaultRunLoopMode模式下才能觸發
// runLoop會找到NSDefaultRunLoopMode,然后把timer添加NSDefaultRunLoopMode中的Timer數組中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//在當前線程中RunLoop添加一個timer, 并告訴runLoop, 這個timer只能在NSRunLoopCommonModes模式下才能觸發
//runLoop會找到NSDefaultRunLoopMode和UITrackingRunLoopMode
//然后把timer添加NSDefaultRunLoopMode和UITrackingRunLoopMode中的Timer數組中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//利用此方法創建的NSTimer, 系統會自動放入當前線程中的currentRunLoop中,并且只能在NSDefaultRunLoop模式下才能觸發
NSTimer * timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
//雖然通過類方法scheduledTimerWithTimeInterval創建NSTimer,會自動添加到NSDefaultRunLoopMode模式中
//但我們還是可以修改它的模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
CFRunLoopSoruceRef
按照官方文檔,source的分類:
Port-Based Sources:基于端口的事件源:監聽程序響應的端口,基于端口事件是由系統內核自動發送的.
Custom Input Sources: 自定義輸入源:監聽自定義事件源,而自定義的輸入源是需要人工從其他線程發送
Cocoa Perfrom Selector Source: selector事件源
按照源碼函數調用棧,source的分類:
Source0:非基于Prot(端口)的,是用戶主動觸發的事件
Source1:基于Prot(端口)的,通過內核和其他線程相互發送消息
CFRunLoopObserverRef
CFRunLoopObserverRef:觀察者對象,可以監聽RunLoop的狀態
RunLoop狀態:
kCFRunLoopEntry 即將進入runLoop
kCFRunLoopBeforeTimers 即將處理Timer
kCFRunLoopBeforeSources 即將處理source(事件源)
kCFRunLoopBeforeWaiting 即將進入休眠
kCFRunLoopAfterWaiting 即將從休眠中醒來
kCFRunLoopExit 即將退出runLoop
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//創建一個CFRunLoopObserverRef
/*第一個參數: CFRunLoopObserverRef(觀察者)分配內存空間方式第二個參數: 監聽那些狀態 kCFRunLoopAllActivities(監聽所有狀態)第三個參數: 是否每次都要監聽第四個參數: 優先級第五個參數: 回調函數*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES,0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// observer監聽對象
//activity Runloop當前狀態
});
/*第一個參數: 為那個線程下的RunLoop添加CFRunLoopObserverRef(觀察者)第二個參數: 需要添加的CFRunLoopObserverRef(觀察者)第三個參數: 把監聽添加到RunLoop那個模式中
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//記得內存管理,因為Core Foundation不在ARC管理范圍內
//帶有Create、Copy、Retain等字眼的函數,創建出來的對象,都需要在最后做一次release
//銷毀對象函數:CFRelease對象CFRelease(observer);
}
RunLoop處理邏輯
如果RunLoop中沒有Timer或source,RunLoop就會立刻退出
每次運行RunLoop,RunLoop會自動處理之前未處理的消息,并通知相關觀察者.具體順序如下:
1.通知觀察者RunLoop已經啟動
2.通知觀察者即將開始啟動定時器
3.通知觀察者即將啟動非基于端口的事件源
4.啟動任何準備好的非基于端口的事件源
5.如果基于端口的事件源準備好并處于等待得狀態,立即啟動.并進入步驟9
6.通知觀察者線程進入休眠
7.將線程置于休眠直到任意下面的事件發生:
某一事件到達基于端口的源
定時器啟動
RunLoop設置的時間已經超時.(系統底層會給RunLoop設置一個超時時間,源碼中設置的是:9999999999.0)
RunLoop被手動喚醒
8.通知觀察者線程將被喚醒.
9.處理未處理的事件
如果用戶定義的定時器啟動,處理定時器事件并重啟RunLoop.進入步驟2
如果事件源啟動,傳遞相應的消息
如果RunLooop被顯示喚醒而且時間還沒超時,重啟RunLoop.進入步驟2
10.通知觀察者RunLoop結束.
如何讓子線程成為常駐線程(讓一個子線程不進入消亡狀態,等待其他線程發來消息,處理其他事件)
(#)import "ViewControllerRunLoop.h"
@interface ViewControllerRunLoop ()
@property(nonatomic,strong)NSThread * thread;
@end
@implementation ViewControllerRunLoop
-(void)viewDidLoad {
//創建子線程執行任務 self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [self.thread start];
}
-(void)run {
NSLog(@"跑起來");
//默認情況下,子線程是不會常駐的
//只有子線程中runloop啟動,并且runloop中有source或timer,才會常駐
//只有常駐線程才能再次執行任務,因為線程中有runloop來處理事件了
//子線程的runloop是需要手動創建的, 并且需要手動啟動
NSRunLoop * rl = [NSRunLoop currentRunLoop];
//如果子線程的runloop沒有 source / timer 的話, 哪么子線程的runloop會立即關閉
//在runLoop中添加一個timer
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector: @selector(timerRun) userInfo:nil repeats:YES];
//啟動runloop [rl run];
//如果線程成為了常駐線程,你會發現,不會執行到這行代碼
//也就是說這個方法不會執行完,
NSLog(@"end");
}
-(void)timerRun{
NSLog(@"%s",__func__);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 讓子線程再次執行任務
[self performSelector:@selector(againRun) onThread:self.thread withObject:nil waitUntilDone:NO];
}
-(void)againRun{
NSLog(@"再次跑起來");
}
@end