目前正是找工作、換工作的黃金時(shí)間,對于iOS開發(fā)從業(yè)者來說 面試?yán)@不過去的就是runLoop,那么我們說到runLoop我們應(yīng)該說些什么呢
答題從以下幾方面入手
1、runLoop是什么
2、runLoop的作用是什么
3、runLoop和線程的關(guān)系
4、runLoop之Modes
5、modes之items(source0,source1,timer、observer)
6、runLoop的應(yīng)用
1、runLoop是什么
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* 獲取modeList的鎖 */
__CFPort _wakeUpPort; // 喚醒runloop
Boolean _unused;
volatile _per_run_data *_perRunData;
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
首先runloop是一個(gè)運(yùn)行循環(huán),它實(shí)際上也是一個(gè)對象、這個(gè)對象提供了一個(gè)入口函數(shù),使程序進(jìn)入一個(gè)do..while的循環(huán)中,循環(huán)處理各種任務(wù)
2、runLoop的作用是什么
1)由于是運(yùn)行循環(huán),他保持程序持續(xù)的運(yùn)行,即便沒有待處理的任務(wù) 也不退出程序(可利用這點(diǎn)防止程序崩潰)
2)處理App中的各種事件 包括觸摸、滑動(dòng)、performSelector等
3)節(jié)省cpu資源,提高程序的性能,使cpu該做事做事,該休息休息
3、runLoop和線程的關(guān)系
1)線程和runLoop是一一對應(yīng)的關(guān)系,
2)主線程runloop程序啟動(dòng)后自動(dòng)創(chuàng)建,子線程默認(rèn)不創(chuàng)建runloop,直到在子線程中第一次獲取runloop時(shí),才創(chuàng)建runloop,同時(shí)runloop存放在一個(gè)可變字典中 字典的key-value 分別為pthread-runloop
4、runLoop之Modes
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // timer開啟后為true
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
}
每個(gè)runloop包含多個(gè)mode,但是 同時(shí)一個(gè)runloop只能在一中mode下運(yùn)行,runloop在運(yùn)行時(shí),是在不斷切換mode的,mode的分類
kCFRunLoopDefaultMode:APP的默認(rèn)mode 通常主線程是在這個(gè)model下運(yùn)行
UITrackingRunLoopMode: 界面跟蹤mode,用于scrollview追蹤觸摸滑動(dòng),保證頁面滑動(dòng)時(shí),不受其他mode的影響
UIInitializationRunLoopMode:當(dāng)app剛啟動(dòng)時(shí),進(jìn)入的第一個(gè)mode,啟動(dòng)完成后就不再使用,會切換到kCFRunLoopDefaultMode
GSEventReceiveRunLoopMode:接收系統(tǒng)事件的內(nèi)部mode,通常用不到
kCFRunLoopCommonModes: 這是一個(gè)占位mode,作為標(biāo)記kCFRunLoopDefaultMode和UITrackingRunLoopMode,用并不是一種真正的model,也可以認(rèn)為是一種混合模式
5、modes之items(source0,source1,timer、observer)
一個(gè)mode持有的source、timer、observer,都是集合類型所以說每個(gè)model,可以持有多個(gè)source、timer、observer
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* 不可變 */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; /* 不可變 */
CFTimeInterval _tolerance; /* 可變 */
uint64_t _fireTSR;
CFIndex _order;
CFRunLoopTimerCallBack _callout;
CFRunLoopTimerContext _context;
};
運(yùn)行的時(shí)候 會將相應(yīng)的item添加到對應(yīng)的mode中,即調(diào)用CFSetAddValue方法,之后再在運(yùn)行循環(huán)時(shí)會調(diào)用CFRunLoopDoBlocks方法,判斷當(dāng)前model后,執(zhí)行相應(yīng)的block回調(diào)
CFRunLoopSourceRef: 包括source0 和 source1
source0,處理app內(nèi)部事件,app自己負(fù)責(zé)管理,uiEvent,CFSocket,僅包含一個(gè)函數(shù)指針
底層調(diào)用 創(chuàng)建source0源,將source0加入當(dāng)runloop相應(yīng)的mode中、執(zhí)行signal信號,標(biāo)記待執(zhí)行,執(zhí)行CFRunLoopWakeUp喚醒runloop,處理相應(yīng)的事件,取消移除源CFRunLoopRemoveSource
source1,包含一個(gè)mach_port和一個(gè)回調(diào)指針,一般用于通過內(nèi)核和其他線程進(jìn)行通訊
CFRunLoopTimerRef:timer的底層是一個(gè)CFRunLoopTimerRef,這個(gè)timer是受runloop的mode模式影響的,
創(chuàng)建CFRunLoopTimerRef 添加到相應(yīng)的當(dāng)前runloop,若是子線程的runloop 需要調(diào)用CFRunLoopRun自行開啟,否則 timer是不會調(diào)用的
CFRunLoopObserverRef:觀察者,觀察runloop所處的狀態(tài)
初始化CFRunLoopObserverContext,
創(chuàng)建 CFRunLoopObserverRef,配置回調(diào)方法
添加觀察者當(dāng)當(dāng)前的runloop
6、runLoop的應(yīng)用
1)線程?;?/p>
[[NSRunLoop currentRunLoop] addPort:[NSort port] forMode:NSDefaultRunLoopMode];
while (self.keepAlive) {
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
退出線程時(shí)
self.keepAlive = NO;
CFRunLoopStop(CFRunLoopGetCurrent());
2)主線程卡頓檢測
在主線程穿件觀察者,在子線程中觀察observer的狀態(tài),
主線程的操作是 在 kCFRunLoopBeforeSource 和 kCFRunLoopBeforeWaiting分別記錄時(shí)間,判斷時(shí)間差 超過某一個(gè)閾值即認(rèn)為發(fā)生了卡頓,此時(shí)可以獲取堆棧信息,并記錄上傳,以供分析
@interface Minitor ()
@property(nonatomic, strong) NSThread *monitorThread;
@property(nonatomic, strong) NSDate *startDate;
/// 是否正在執(zhí)行任務(wù)
@property(nonatomic, assign, getter=isExcuting) BOOL excuting;
@end
@implementation Minitor {
CFRunLoopObserverRef _observer;
CFRunLoopTimerRef _timer;
}
+ (instancetype)shareInstatance {
static Minitor *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[Minitor alloc] init];
instance.monitorThread = [[NSThread alloc] initWithTarget:self selector:@selector(moniterThreadEntryPoint) object:nil];
[instance.monitorThread start];
});
return instance;
}
+ (void)moniterThreadEntryPoint {
@autoreleasepool {
[[NSThread currentThread] setName:@"Monitor"];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runloop run];
}
}
- (void)statrMonitor {
if (_observer) {
NSLog(@"已經(jīng)創(chuàng)建了監(jiān)聽");
return;
}
CFRunLoopObserverContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
[self performSelector:@selector(addTimerToMonitorThread) onThread:self.monitorThread withObject:nil waitUntilDone:NO modes:@[NSRunLoopCommonModes]];
}
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
Minitor *monitor = (__bridge Minitor*)info;
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
monitor.startDate = [NSDate date];
monitor.excuting = YES;
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
monitor.excuting = NO;
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
#pragma mark 定時(shí)器
- (void)addTimerToMonitorThread {
if (_timer) {
return;
}
CFRunLoopRef curentRunLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
_timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.01, 0.01, 0, 0, &runloopTimerCallBack, &context);
CFRunLoopAddTimer(curentRunLoop, _timer, kCFRunLoopCommonModes);
}
static void runloopTimerCallBack(CFRunLoopTimerRef timer, void *info) {
Minitor *monitor = (__bridge Minitor*)info;
if (!monitor.isExcuting) {
// kCFRunLoopBeforeWaiting
return;
}
NSTimeInterval excuteTime = [[NSDate date] timeIntervalSinceDate:monitor.startDate];
NSLog(@"定時(shí)器:當(dāng)前線程%@,主線程執(zhí)行時(shí)間:%f秒", [NSThread currentThread], excuteTime);
if (excuteTime >= 0.00001) {
NSLog(@"卡頓了 %f 秒", excuteTime);
[monitor handleStackInfo];
}
}
- (void)handleStackInfo {
NSArray *callStackSymbls = [NSThread callStackSymbols];
[callStackSymbls enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@", obj);
}];
}
@end
3)給程序賦予一次回光返照的機(jī)會
參開鏈接傳送門
4)大型列表 加載圖片時(shí),即圖片下載或是顯示 根據(jù)runloop當(dāng)前為 空閑狀態(tài),才開始下載或是顯示圖片,以免顯示圖片造成頁面卡頓