Runloop :運行循環
- APP啓動,操作系統會開啓一條線程,這就是這個APP的主線程;
- 這個主線程是一個常駐線程,因爲這條線程上邊的Runloop 被開啓了;
Runloop 作用
- 保證線程不退出;
- 負責監聽所有的事件。 如: 觸摸、時鐘、網絡事件...
Runloop 的模式(Mode)
- NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timeSelect) userInfo:nil repeats:YES];
- [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeSelect) userInfo:nil repeats:YES];
以上這兩個timer的實現, 結果是: 函數1不會執行,函數2會執行。原因就是函數2內部已經添加了runLoop。而函數1, 需要手動添加。 那麼函數1需要再添加: [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 即可執行time操作。
- Runloop共有5中模式:(常用的其實只有兩種)
- NSDefaultRunLoopMode // 默認模式
- UITrackingRunLoopMode // UI模式, 優先級高於默認模式
- NSRunLoopCommonModes // 佔位模式,(並不是runloop 的真正模式,但功能相當於 默認模式+UI模式)
- 程序初始化模式 // 操作系統調用
- 程序內核模式 // 操作系統調用
NSDefaultRunLoopMode : 首先在沒有事件發生時候runloop處於休眠狀態,當有事件需要執行或響應, runloop會被喚醒做一次執行循環,執行結束繼續處於休眠狀態。 如果此時有觸摸事件發生( 比如 拖拽事件或者觸摸事件)那麼runloop 會優先處理觸摸事件,而忽略timer事件。timer 的selector 也不會被執行。當鬆開手 timer 會繼續執行。
UITrackingRunLoopMode :簡稱 UI模式, runloop 只會觸發UI事件,當觸摸事件發生的時候才runloop 纔會執行。而此時 默認模式即使有事件執行,runloop也不會理會。
NSRunLoopCommonModes: 佔位模式,並不是lunloop真正的模式。 所謂佔位指的是在添加了NSDefaultRunLoopMode 模式,也添加了TrackingRunLoopMode 模式。 那麼此時表現也是二合一。 正常執行的timer在發生觸摸時也會繼續執行。但是!!!timer 中如果有耗時操作, 將會造成UI頁面卡頓。 怎麼解決呢? 當然是放在子線程中!
Runloop 與多線程
NSThread *tread = [[NSThread currentThread]initWithBlock:^{
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timeSelect) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop]run];
}];
[tread start];
這樣 當前子線程的runloop 會運行起來!子線程會一直存在,即使timer停止。 相當於一個死循壞!!!
那麼要想銷燬當前子線程,記得在子線程中調用 [NSThread exit];
如果在主線程中調用 [NSThread exit], 那麼主線程會退出, 子線程依舊還在運行。隨着主線程的退出,頁面點擊、拖拽等事件都不會再響應。那爲什麼主線程掛掉之後爲什麼UI頁面都還在顯示? 因爲UI顯示是顯示在操作系統上,UIApplication無法處理界面上的事情了,而界面是傳遞給主線程,主線程已掛斷掉 此時runloop 不在處理界面上任何事情。
Runloop 的source
source: 事件源(輸入源)按照函數調用棧分爲
source0:非系統內核事件;
source1: 系統內核事件;
GCD timer, 直接上代碼吧
// 創建
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
// 設置timer 時間單位是納秒 1秒 = 1000000000納秒
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0);
// 設置回調
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"current thread --- %@",[NSThread currentThread]);
});
// 啓動
dispatch_resume(self.timer);
根據執行結果可知, GCD timer 默認已經將Runloop 添加進去了。
Runloop 的Observer
用於觀察runloop循環, 代碼 :
- (void)viewDidLoad {
[super viewDidLoad];
// 防止runloop 休眠
[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(timerMeather) userInfo:nil repeats:YES];
// 添加觀察者
[self addRunLoopObserver];
}
-(void)timerMeather
{
}
-(void)addRunLoopObserver{
// 獲取當前RunLoop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
// 定義一個context
CFRunLoopObserverContext context = {
0,
(__bridge void *)self,
&CFRetain,
CFRelease,
NULL
};
// 定義觀察者
static CFRunLoopObserverRef defaultModeObserver;
//創建觀察者
defaultModeObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &callBack, &context);
// 添加觀察者
CFRunLoopAddObserver(runloop, defaultModeObserver, kCFRunLoopDefaultMode);
}
// 觀察runloop回調
static void callBack(CFRunLoopObserverRef obderver,CFRunLoopActivity activity ,void *info){
// info 當前控制器, 添加觀察者時添加的context目的。
NSLog(@"come ");
}
添加代碼會發現,剛啓動的時候回執行幾次,然後就不執行了,或者偶爾執行以下。 那是因爲runloop 沒任務執行,處於休眠狀態。 那麼我們給執行一個timer,讓timer運行起來。 此時runloop 也就不會休眠了。