RunLoop
顧名思義
- 運行循環(huán)
- 在程序運行過程中循環(huán)做一些事情
- 一般來講,一個線程一次只能執(zhí)行一個任務奄薇,執(zhí)行完成后線程就會退出。如果我們需要一個機制,讓線程能隨時處理事件但并不退出舟茶,通常的代碼邏輯是這樣的:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
這種模型通常被稱作Event Loop。
實現(xiàn)這種模型的關鍵點在于:如何管理事件/消息堵第,如何讓線程在沒有處理消息時休眠以避免資源占用吧凉、在有消息到來時立即被喚醒。
RunLoop實際上就是一個對象踏志,這個對象管理了其需要處理的事件和消息阀捅,并提供了一個入口函數(shù)來執(zhí)行上面的Event Loop的邏輯。線程執(zhí)行了這個函數(shù)后针余,就會一直處于正常函數(shù)內(nèi)部“接受消息->等待->處理”的循環(huán)中饲鄙,直到這個循環(huán)結(jié)束(比如傳入quit的消息),函數(shù)返回圆雁。
應用范疇
- 定時器(Timer)傍妒、performSelector
- GCD Async Main Queue
- 事件響應、手勢識別摸柄、界面刷新
- 網(wǎng)絡請求
- AutoreleasePool
RunLoop的基本作用
- 保持程序的持續(xù)運行
- 處理App中的各種事件(比如觸摸事件颤练、定時器事件等)
- 節(jié)省CPU資源,提高程序性能:該做事時做事驱负,該休息時休息
RunLoop對象
- iOS中有2套API來訪問和使用RunLoop
- Foundation:NSRunLoop
- Core Foundation:CFRunLoopRef
- NSRunLoop和CFRunLoopRef都代表著RunLoop對象
- NSRunLoop是基于CFRunLoopRef的一層OC包裝(所以runloop的地址會不一樣)
- 提供了面向?qū)ο蟮腁PI嗦玖,但是這些API不是線程安全的
-
CFRunLoopRef是開源的
- 提供了純C函數(shù)的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
- 線程剛創(chuàng)建時并沒有RunLoop對象,RunLoop會在第一次獲取它時創(chuàng)建
- RunLoop會在線程結(jié)束時銷毀
- 主線程的RunLoop已經(jīng)自動獲壤沂酢(創(chuàng)建)器瘪,子線程默認沒有開啟RunLoop
- 只能在一個線程的內(nèi)部獲取其RunLoop(主線程除外)翠储。
RunLoop相關的類
- Core Foundation中關于RunLoop的5個類
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef 事件產(chǎn)生的地方
- 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
};
// 監(jiān)聽事件的狀態(tài)變化(只有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;
}
}
// 創(chuàng)建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);
根據(jù)源碼分析挽牢,得到如下關系圖
RunLoop的運行邏輯
- Source0
- 觸摸事件處理
- performSelector: onThread:(提供一個線程谱煤,就可以讓對應方法去該線程去執(zhí)行)
- Source1
- 基于Port的線程間通信
- 系統(tǒng)事件捕捉
- Timers
- NSTimer
- performSelector: withObject: afterDelay:
- Observers
- 用于監(jiān)聽RunLoop的狀態(tài)
- UI刷新(BeforeWaiting)
- AutoReleasePool(BeforeWaiting)
如下圖所示
RunLoop休眠的實現(xiàn)原理
實質(zhì):從用戶態(tài)切換到內(nèi)核態(tài),去等待消息禽拔,沒有消息就讓線程休眠趴俘,不在占用cpu,有消息就喚醒線程奏赘。
RunLoop在實際開發(fā)中的應用
控制線程生命周期(線程绷壬粒活,比如AFNetWorking都是在子線程中等待的磨淌,所以是一直逼1铮活)
解決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數(shù)組中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
// NSLog(@"%d",++count);
// }];
- 監(jiān)控應用卡頓
- 性能優(yōu)化