簡介
簡單的說run loop是事件驅(qū)動的一個大循環(huán)猾蒂,如下代碼所示
int main(int argc, char * argv[]) {
//程序一直運行狀態(tài)
while (AppIsRunning) {
//睡眠狀態(tài)炕倘,等待喚醒事件
id whoWakesMe = SleepForWakingUp();
//得到喚醒事件
id event = GetEvent(whoWakesMe);
//開始處理事件
HandleEvent(event);
}
return 0;
}
Cocoa會涉及到Run Loops的
- 系統(tǒng)級:GCD斋陪,mach kernel侠草,block辱挥,pthread
- 應(yīng)用層:NSTimer,UIEvent边涕,Autorelease晤碘,NSObject(NSDelayedPerforming),NSObject(NSThreadPerformAddition)功蜓,CADisplayLink园爷,CATransition,CAAnimation式撼,dispatch_get_main_queue()(GCD中dispatch到main queue的block會被dispatch到main RunLoop執(zhí)行)童社,NSPort,NSURLConnection著隆,AFNetworking(這個第三方網(wǎng)絡(luò)請求框架使用在開啟新線程中添加自己的run loop監(jiān)聽事件)
在Main thread堆棧中所處位置
堆棧最底層是start(dyld)扰楼,往上依次是main,UIApplication(main.m) -> GSEventRunModal(Graphic Services) -> RunLoop(包含CFRunLoopRunSpecific美浦,__CFRunLoopRun弦赖,__CFRunLoopDoSouces0,CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION) -> Handle Touch Event
RunLoop原理
CFRunLoop開源代碼:http://opensource.apple.com/source/CF/CF-855.17/
執(zhí)行順序的偽代碼
SetupThisRunLoopRunTimeoutTimer(); // by GCD timer
do {
__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks();
__CFRunLoopDoSource0();
CheckIfExistMessagesInMainDispatchQueue(); // GCD
__CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
var wakeUpPort = SleepAndWaitForWakingUpPorts();
// mach_msg_trap
// Zzz...
// Received mach_msg, wake up
__CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
// Handle msgs
if (wakeUpPort == timerPort) {
__CFRunLoopDoTimers();
} else if (wakeUpPort == mainDispatchQueuePort) {
// GCD
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
} else {
__CFRunLoopDoSource1();
}
__CFRunLoopDoBlocks();
} while (!stop && !timeout);
構(gòu)成
Thread包含一個CFRunLoop浦辨,一個CFRunLoop包含一種CFRunLoopMode蹬竖,mode包含CFRunLoopSource,CFRunLoopTimer和CFRunLoopObserver荤牍。
CFRunLoopMode
RunLoop只能運行在一種mode下案腺,如果要換mode當前的loop也需要停下重啟成新的。利用這個機制康吵,ScrollView過程中NSDefaultRunLoopMode的mode會切換UITrackingRunLoopMode來保證ScrollView的流暢滑動不受只能在NSDefaultRunLoopMode時處理的事件影響滑動劈榨。同時mode還是可定制的。
- NSDefaultRunLoopMode:默認晦嵌,空閑狀態(tài)
- UITrackingRunLoopMode:ScrollView滑動時
- UIInitializationRunLoopMode:啟動時
- NSRunLoopCommonModes:Mode集合
Timer計時會被scrollView的滑動影響的問題可以通過將timer添加到NSRunLoopCommonModes來解決
//將timer添加到NSDefaultRunLoopMode中
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerTick:)
userInfo:nil
repeats:YES];
//然后再添加到NSRunLoopCommonModes里
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:@selector(timerTick:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopTimer
NSTimer是對RunLoopTimer的封裝
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
CFRunLoopSource
- source0:處理如UIEvent同辣,CFSocket這樣的事件
- source1:Mach port驅(qū)動拷姿,CFMachport,CFMessagePort
CFRunLoopObserver
Cocoa框架中很多機制比如CAAnimation等都是由RunLoopObserver觸發(fā)的旱函。observer到當前狀態(tài)的變化進行通知响巢。
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
使用RunLoop的案例
AFNetworking
使用NSOperation+NSURLConnection并發(fā)模型都會面臨NSURLConnection下載完成前線程退出導(dǎo)致NSOperation對象接收不到回調(diào)的問題。AFNetWorking解決這個問題的方法是按照官方的guidhttps://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLConnection/initWithRequest:delegate:startImmediately:上寫的NSURLConnection的delegate方法需要在connection發(fā)起的線程runloop中調(diào)用棒妨,于是AFNetWorking直接借鑒了Apple自己的一個Demohttps://developer.apple.com/LIBRARY/IOS/samplecode/MVCNetworking/Introduction/Intro.html的實現(xiàn)方法單獨起一個global thread踪古,內(nèi)置一個runloop,所有的connection都由這個runloop發(fā)起券腔,回調(diào)也是它接收伏穆,不占用主線程,也不耗CPU資源纷纫。
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread =
[[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
類似的可以用這個方法創(chuàng)建一個常駐服務(wù)的線程枕扫。
TableView中實現(xiàn)平滑滾動延遲加載圖片
利用CFRunLoopMode的特性,可以將圖片的加載放到NSDefaultRunLoopMode的mode里辱魁,這樣在滾動UITrackingRunLoopMode這個mode時不會被加載而影響到烟瞧。
UIImage *downloadedImage = ...;
[self.avatarImageView performSelector:@selector(setImage:)
withObject:downloadedImage
afterDelay:0
inModes:@[NSDefaultRunLoopMode]];
接到程序崩潰時的信號進行自主處理例如彈出提示等
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
while (1) {
for (NSString *mode in allModes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
異步測試
- (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout
{
__block Boolean fulfilled = NO;
void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) =
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
fulfilled = block();
if (fulfilled) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
};
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// Run!
CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
return fulfilled;
}