RunLoop
顧名思義
- 運行循環
- 在程序運行過程中循環做一些事情
- 一般來講,一個線程一次只能執行一個任務,執行完成后線程就會退出。如果我們需要一個機制,讓線程能隨時處理事件但并不退出,通常的代碼邏輯是這樣的:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
這種模型通常被稱作Event Loop。
實現這種模型的關鍵點在于:如何管理事件/消息,如何讓線程在沒有處理消息時休眠以避免資源占用、在有消息到來時立即被喚醒。
RunLoop實際上就是一個對象,這個對象管理了其需要處理的事件和消息,并提供了一個入口函數來執行上面的Event Loop的邏輯。線程執行了這個函數后,就會一直處于正常函數內部“接受消息->等待->處理”的循環中,直到這個循環結束(比如傳入quit的消息),函數返回。
應用范疇
- 定時器(Timer)、performSelector
- GCD Async Main Queue
- 事件響應、手勢識別、界面刷新
- 網絡請求
- AutoreleasePool
RunLoop的基本作用
- 保持程序的持續運行
- 處理App中的各種事件(比如觸摸事件、定時器事件等)
- 節省CPU資源,提高程序性能:該做事時做事,該休息時休息
RunLoop對象
- iOS中有2套API來訪問和使用RunLoop
- Foundation:NSRunLoop
- Core Foundation:CFRunLoopRef
- NSRunLoop和CFRunLoopRef都代表著RunLoop對象
- NSRunLoop是基于CFRunLoopRef的一層OC包裝(所以runloop的地址會不一樣)
- 提供了面向對象的API,但是這些API不是線程安全的
-
CFRunLoopRef是開源的
- 提供了純C函數的API,所有這些API都是線程安全的
- NSRunLoop是基于CFRunLoopRef的一層OC包裝(所以runloop的地址會不一樣)
// OC獲取當前的RunLoop
NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
// OC獲取主線程RunLoop
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
// C語言獲取當前的RunLoop
CFRunLoopRef currentRunloop2 = CFRunLoopGetCurrent();
// C語言獲取主線程的RunLoop
CFRunLoopRef mainRunloop2 = CFRunLoopGetMain();
RunLoop與線程
- 每條線程都有唯一的一個與之對應的RunLoop對象
- RunLoop保存在一個全局的Dictionary里,線程作為key,RunLoop作為value
- 線程剛創建時并沒有RunLoop對象,RunLoop會在第一次獲取它時創建
- RunLoop會在線程結束時銷毀
- 主線程的RunLoop已經自動獲取(創建),子線程默認沒有開啟RunLoop
- 只能在一個線程的內部獲取其RunLoop(主線程除外)。
RunLoop相關的類
- Core Foundation中關于RunLoop的5個類
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef 事件產生的地方
- CFRunLoopTimerRef
- CFRunLoopObserverRef
CFRunLoopRef
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread; // RunLoop所對應的線程
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode; // _modes這個集合里面只有一個模式被稱為當前模式_currentMode
CFMutableSetRef _modes; // 這個集合里面裝了很多CFRunLoopModeRef類型的對象
};
CFRunLoopModeRef
- CFRunLoopModeRef代表RunLoop的運行模式
- 一個RunLoop包含若干個Mode,每個Mode又包含若干個Source0/Source1/Timer/Observer
- RunLoop啟動時只能選擇其中一個Mode,作為currentMode
- 如果需要切換Mode,只能退出當前Loop,在重新選擇一個Mode進入
- 目的:不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響
- 如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出
常見的2種Mode
- kCFRunLoopDefaultMode(等價于NSDefaultRunLoopMode):App的默認Mode,通常主線程是在這個Mode下運行
- UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView追蹤觸摸滑動,保證界面滑動時不受其他Mode影響
- 另外還有一種模式:kCFRunLoopCommonModes 默認包括kCFRunLoopDefaultMode、UITrackingRunLoopMode兩種模式
typedef struct __CFRunLoopMode * CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name; // mode的名稱
CFMutableSetRef _sources0;
CFMutableSetRef _sources1; // source里面裝了很多CFRunLoopSourceRef類型的對象
CFMutableArrayRef _observers;// 里面裝了很多CFRunLoopObserverRef類型的對象
CFMutableArrayRef _timers; // 里面裝了很多CFRunLoopTimerRef類型的對象
};
CFRunLoopObserverRef
/* RunLoop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),// 即將進入Loop
kCFRunLoopBeforeTimers = (1UL << 1),// 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
kCFRunLoopAllActivities = 0X0FFFFFFFU
};
// 監聽事件的狀態變化(只有c語言接口)
// 方法一
void observeRunLoopActivities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
// 創建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActivities, NULL);
// 添加observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 釋放observer
CFRelease(observer);
// 方法二
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@",mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit:{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit- %@",mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// 添加observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 釋放observer
CFRelease(observer);
根據源碼分析,得到如下關系圖
runloop
RunLoop的運行邏輯
- Source0
- 觸摸事件處理
- performSelector: onThread:(提供一個線程,就可以讓對應方法去該線程去執行)
- Source1
- 基于Port的線程間通信
- 系統事件捕捉
- Timers
- NSTimer
- performSelector: withObject: afterDelay:
- Observers
- 用于監聽RunLoop的狀態
- UI刷新(BeforeWaiting)
- AutoReleasePool(BeforeWaiting)
如下圖所示
runloop流程
RunLoop休眠的實現原理
休眠的實現原理
實質:從用戶態切換到內核態,去等待消息,沒有消息就讓線程休眠,不在占用cpu,有消息就喚醒線程。
RunLoop在實際開發中的應用
控制線程生命周期(線程保活,比如AFNetWorking都是在子線程中等待的,所以是一直保活)
解決NSTimer在滑動時停止工作的問題
static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d",++count);
}];
// NSDefaultRunLoopMode/UITrackingRunLoopMode才是真正存在的模式
// NSRunLoopCommonModes并不是一個真的模式,它只是一個標記
// timer能在_commonModes數組中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
// NSLog(@"%d",++count);
// }];
- 監控應用卡頓
- 性能優化