原文鏈接:https://blog.csdn.net/u014795020/article/details/72084735
前言
RunLoop 是 iOS 和 OSX 開(kāi)發(fā)中非程洌基礎(chǔ)的一個(gè)概念,為了讓大家更加快速融入街望,請(qǐng)先一段代碼:
+ (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;
}
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
以上是AFN2.x的一段經(jīng)典代碼
首先我們要明確一個(gè)概念医咨,線程一般都是一次執(zhí)行完畢任務(wù)扛拨,就銷毀了
来涨。
而在線程中添加了runloop
惕味,并運(yùn)行起來(lái)扇调,實(shí)際上是添加了一個(gè)do榛了,while循環(huán)
在讶,這樣這個(gè)線程的程序就一直卡在do,while循環(huán)上
霜大,這樣相當(dāng)于線程的任務(wù)一直沒(méi)有執(zhí)行完
构哺,所以線程一直不會(huì)銷毀
。
所以,一旦我們添加了一個(gè)runloop曙强,并run了残拐,我們?nèi)绻N毀這個(gè)線程,必須停止runloop碟嘴,至于停止的方式溪食,我們接著往下看。
這里創(chuàng)建了一個(gè)線程娜扇,取名為AFNetworking错沃,因?yàn)樘砑恿艘粋€(gè)runloop,所以這個(gè)線程不會(huì)被銷毀雀瓢,直到runloop停止枢析。
[runloop addPort: [NSMachPort port] forMode: NSDefaultRunLoopMode];
這行代碼的目的是添加一個(gè)端口監(jiān)事件,這也是我們后面會(huì)講到的一種線程間的通信方式-基于端口的通信致燥。
[runloop run];
runloop開(kāi)始跑起來(lái)登疗,但是要注意,這種runloop嫌蚤,只有一種方式能停止辐益。
[NSRunloop currentRunloop] removePort: <#(nonnull NSPort)#> forMode: <#(nonull NSRunLoopMode)#>
只有從runloop中移除我們之前添加的端口,這樣runloop就沒(méi)有任何事件脱吱,所以runloop會(huì)直接退出智政。
再次回到AFN2.x的這行源碼上,因?yàn)樗玫氖莚un箱蝠,而且并沒(méi)有記錄下自己添加的NSMachPort,所有顯然续捂,它沒(méi)有打算退出這個(gè)runloop,這是一個(gè)常駐線程
宦搬。事實(shí)上牙瓢,看過(guò)AFN2.x源碼的同學(xué)都會(huì)知道,這個(gè)thread需要常駐的原因间校,在此就不做贅述了矾克。
接下來(lái)我們看看AFN3.x是怎么用runloop的:
需要開(kāi)啟的時(shí)候:
CFRunLoopRun();
終止的時(shí)候:
CFRunloopStop(CFRunLoopGetCurrent());
由于NSUrlSession參考了AFN2.x的優(yōu)點(diǎn),自己維護(hù)了一個(gè)線程池憔足,做Request線程的調(diào)度與管理胁附,所以在AFN3.x中,沒(méi)有了常駐線程滓彰,都是用的run控妻,結(jié)束的時(shí)候stop。
再看RAC中runloop:
do {
[NSRunloop.mainRunloop runMode:NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
} while(!done);
大致講下這段代碼實(shí)現(xiàn)的內(nèi)容揭绑,自己用一個(gè)Bool值done去控制runloop的運(yùn)行弓候,每次只運(yùn)行這個(gè)模式的runloop,0.1秒。0.1秒后開(kāi)啟runloop的下次運(yùn)行菇存。
以上我們都大致分析一下彰居,后面我們?cè)賮?lái)講為什么。
首先我們講講runloop的概念
Runloop撰筷,顧名思義就是跑圈,他的本質(zhì)就是一個(gè)do畦徘,while
循環(huán)毕籽,當(dāng)有事做時(shí)就做事,沒(méi)事做時(shí)就休眠井辆。至于怎么做事关筒,怎么休眠,這個(gè)是由系統(tǒng)內(nèi)核來(lái)調(diào)度的,我們后面會(huì)講到杯缺。
每個(gè)線程都由一個(gè)Run Loop
蒸播,主線程的Run Loop會(huì)在App運(yùn)行的時(shí)自動(dòng)運(yùn)行
,子線程需要手動(dòng)獲取運(yùn)行
萍肆,第一次獲取時(shí)袍榆,才會(huì)去創(chuàng)建。
每個(gè)Run Loop都會(huì)以一個(gè)模式mode來(lái)運(yùn)行塘揣,可以使用NSRunLoop的方法運(yùn)行在某個(gè)特定的mode包雀。
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
Run Loop的處理兩大類事件源:Timer Source和Input Source(包括performSelector *方法簇、Port或者自定義的Input Source)亲铡,每個(gè)事件源都會(huì)綁定在Run Loop的某個(gè)特定模式mode上才写,而且只有RunLoop在這個(gè)模式下運(yùn)行的時(shí)候,才會(huì)觸發(fā)Timer和Input Source奖蔓。
最后赞草,如果沒(méi)有把事件源添加到Run Loop上,Run Loop就會(huì)立刻exit吆鹤,這也是一開(kāi)始AFN例子厨疙,為什么需要綁定一個(gè)Port的原因。
我們先來(lái)談?wù)凴unLoop Mode
OS下Run Loop的主要運(yùn)行模式mode有:
1)NSDefaultRunLoopMode
:默認(rèn)的運(yùn)行模式
檀头,除了NSConnection對(duì)象的事件轰异。
2)NSRunLoopCommonModes
:是一組常用的模式集合,將一個(gè)input source關(guān)聯(lián)到這個(gè)模式集合上暑始,等于將input source關(guān)聯(lián)到這個(gè)模式集合中的所有模式上搭独。在iOS系統(tǒng)中NSRunLoopCommonMode包含NSDefaultRunLoopMode、NSTaskDeathCheckMode廊镜、UITrackingRunLoopMode
牙肝。
假如我有個(gè)timer要關(guān)聯(lián)到這些模式上,一個(gè)個(gè)注冊(cè)很麻煩,我可以用:
CFRunLoopAddCommonMode([[NSRunLoop currentRunLoop] getCFRunLoop],(__bridge CFStringRef) UITrackingRunLoopMode);
將UITrackingRunLoopMode
或者其他模式添加到
這個(gè)NSRunLoopCommonModes模式
中配椭,然后只需要將Timer關(guān)聯(lián)到NSRunLoopCommonModes
虫溜,即可以實(shí)現(xiàn)RunLoop運(yùn)行在這個(gè)模式集合中任意一個(gè)模式時(shí),這個(gè)Timer都可以觸發(fā)
股缸。
當(dāng)然衡楞,默認(rèn)情況下NSRunLoopCommonModes包含了NSDefaultRunLoopMode和UITrackingRunLoopMode。我指的是如果有其他自定義的Mode敦姻。
注意: 讓Run Loop運(yùn)行在NSRunLoopCommonModes下是沒(méi)有意義的瘾境,因?yàn)橐粋€(gè)時(shí)刻Run Loop只能運(yùn)行在一個(gè)特定模式下,而不可能是個(gè)模式集合镰惦。
3)UITrackingRunLoopMode:用于跟蹤觸摸事件觸發(fā)的模式(例如UIScrollView上下滾動(dòng))迷守, 在主線程中觸摸事件
會(huì)設(shè)置為這個(gè)模式,可以用來(lái)在控件事件觸發(fā)過(guò)程中設(shè)置Timer旺入。
- GSEventReceiveRunLoopMode:用于接受系統(tǒng)事件兑凿,屬于內(nèi)部的Run Loop模式。
5)自定義Mode:可以設(shè)置自定義的運(yùn)行模式Mode茵瘾,你也可以用CFRunLoopAddCommonMode
添加到NSRUnLoopCommonModes
中礼华。
總結(jié)一下:
Run Loop 運(yùn)行時(shí)只能以一種固定的模式運(yùn)行,如果我們需要它切換模式龄捡,只有停掉它卓嫂,再重新開(kāi)其它。
運(yùn)行時(shí)它只會(huì)監(jiān)控這個(gè)模式下添加的Timer Source和Input Source聘殖,如果這個(gè)模式下沒(méi)有相應(yīng)的事件源晨雳,RunLoop的運(yùn)行也會(huì)立刻返回的。注意Run Loop不能在NSRunLoopCommonModes模式運(yùn)行奸腺,因?yàn)镹SRunLoopCommonModes其實(shí)是個(gè)模式集合餐禁,而不是一個(gè)具體的模式
,我可以添加事件源的時(shí)候使用NSRunLoopCommonModes突照,只要Run Loop運(yùn)行在NSRunLoopCommonModes中任何一個(gè)模式帮非,這個(gè)事件源都可以被觸發(fā)
。
Run Loop運(yùn)行接口
要操作Run Loop讹蘑,F(xiàn)oundation層和Core Foundation層都有相應(yīng)的接口可以操作Run Loop:Foundation層對(duì)應(yīng)的是NSRunLoop末盔,Core Foundation層對(duì)應(yīng)的是CFRunLoopRef;
Foundation層對(duì)應(yīng)的是NSRunLoop座慰,Core Foundation層對(duì)應(yīng)的是CFRunLoopRef
陨舱;
兩組接口差不多,不過(guò)功能上還是有許多區(qū)別的:
例如CF層可以添加自定義的Input Source事件源版仔、(CFRunLoopSourceRef)RunLoop觀察者Observer(CFRunLoopObserverRef)游盲,很多類似功能的接口特性也是不一樣的误墓。
NSRunLoop的運(yùn)行接口:
// 運(yùn)行NSRunLoop,運(yùn)行模式為默認(rèn)的NSDefaultRunLoopMode模式益缎,沒(méi)有超時(shí)限制
- (void)run谜慌;
// 運(yùn)行NSRunLoop:參數(shù)為時(shí)間期限,運(yùn)行模式為默認(rèn)的NSDefaultRunLoopMode模式
- (void)runUntilDate:(NSDate *)limitDate;
// 運(yùn)行NSRunLoop:參數(shù)為運(yùn)行模式莺奔、時(shí)間期限欣范,返回值為YES表示處理事件后返回的,NO表示是超時(shí)或者停止運(yùn)行導(dǎo)致返回的令哟。
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDtate *)limitDate;
CFRunLoopRef的運(yùn)行接口:
// 運(yùn)行CFRunLoopRef
void CFRunLoopRun();
// 運(yùn)行CFRunLoopRef:參數(shù)為運(yùn)行模式熙卡、時(shí)間和是否在處理Input Source后退出標(biāo)志,返回值是exit原因
SInt32 CFRunLoopRunInMode(mode, second, returnAfterSourceHandled);
// 停止運(yùn)行CFRunLoop
void CFRunLoopStop(CFRunLoopRef rl);
// 喚醒CFRunLoopRef
void CFRunLoopWakeUp(CFRunLoopRef rl);
首先励饵,詳細(xì)講解下NSRunLoop的三個(gè)運(yùn)行接口:
一
- (void)run; // 無(wú)條件運(yùn)行
不建議使用,因?yàn)檫@個(gè)接口會(huì)導(dǎo)致Run Loop永久性的在NSDefaultRunLoopMode模式滑燃。
即使用CFRunLoopStop(runloopRef);也無(wú)法停止Run Loop的運(yùn)行役听,除非能移除這個(gè)runloop上的所有事件源,包括定時(shí)器和source事件表窘,不然這個(gè)子線程就無(wú)法停止典予,只能永久運(yùn)行下去。
二
- (void)runUntilDate:(NSDate *)limitDate; // 有一個(gè)超時(shí)時(shí)間限制
比上面的接口好點(diǎn)乐严,有個(gè)超時(shí)時(shí)間瘤袖,可以控制每次Run Loop的運(yùn)行時(shí)間,也是運(yùn)行在NSDefaultRunLoopMode模式昂验。
這個(gè)方法運(yùn)行Run Loop一段時(shí)間會(huì)退出給你檢查運(yùn)行條件的機(jī)會(huì)捂敌,如果需要可以再次運(yùn)行Run Loop昌执。
注意CFRunLoopStop(runloopRef), 也無(wú)法停止Run Loop的運(yùn)行啄清。
使用如下的代碼:
while(!Done) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 10]];
NSLog(@"exiting runloop, ......");
}
注意這個(gè)Done是我們自定義的一個(gè)Bool值绽昏,用來(lái)控制是否還需要開(kāi)啟下一次runloop暮刃。
這個(gè)例子大概做了如下的事情: 這個(gè)RunLoop會(huì)每10秒退出一次选脊,然后輸出exiting runloop ……,然后下次根據(jù)我們的Done值來(lái)判斷是否再去運(yùn)行runloop鸣个。
三
// 有一個(gè)超時(shí)時(shí)間限制榛斯,而且設(shè)置運(yùn)行模式
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
從方法上來(lái)看绊汹,比上面多了一個(gè)參數(shù)磺箕,可以設(shè)置運(yùn)行模式
奖慌。
注意:這種運(yùn)行方式是可以被CFRunLoopStop(runloopRef)所停止的(大家可以自己寫個(gè)例子試試)。
除此之外松靡,這個(gè)方法和第二個(gè)方法還有一個(gè)很大的區(qū)別简僧,就是這樣去運(yùn)行runloop會(huì)多一種退出方式。這里我指的退出方式是除了timer觸發(fā)以外的事件击困,都會(huì)導(dǎo)致runloop退出涎劈,這里舉個(gè)簡(jiǎn)答的例子:
- (void)testDemo1{
dispatch_async(dispatch_get_global_queue(0,0), ^ {
NSLog(@"線程開(kāi)始");
// 獲取當(dāng)前線程
self.thread = [NSThread currentThread];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
// 添加一個(gè)Port广凸,同理為了防止runloop沒(méi)事干直接退出
[runloop addPort: [NSMachPort port] forMode: NSDefaultRunLoopMode];
// 運(yùn)行一個(gè)runloop, [NSDate distantFuture]:很久很久以后才讓它失效
[runloop runMode:NSDefaultRunloopMode beforeDate: [NSDate distantFuture]];
NSLog(@"線程結(jié)束");
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
// 在我們開(kāi)啟的異步線程調(diào)用方法
[self performSelector:@selector(recieveMsg) onThread: self.thread withObject: nil waitUntilDone: NO];
});
}
- (void)recieveMsg {
NSLog(@"收到消息了蛛枚,在這個(gè)線程:%@"谅海, [NSThread currentThread]);
}
2016-11-22 14:04:15.250 TestRunloop3[70591:1742754] 線程開(kāi)始
2016-11-22 14:04:17.250 TestRunloop3[70591:1742754] 收到消息了,在這個(gè)線程:<NSThread: 0x600000263c80>{number = 3, name = (null)}
2016-11-22 14:04:17.250 TestRunloop3[70591:1742754] 線程結(jié)束
在這里我們用了performSelector: onThread…這個(gè)方法去進(jìn)行線程間的通信蹦浦,這只是其中最簡(jiǎn)單的方式扭吁。但是缺點(diǎn)也很明顯,就是在去調(diào)用這個(gè)線程的時(shí)候盲镶,如果線程已經(jīng)不存在了侥袜,程序就會(huì)crash。后面我們會(huì)仔細(xì)講各種線程間的通信溉贿。
我們看到枫吧,我們收到一個(gè)消息,這個(gè)消息是一個(gè) 非timer得事件宇色,所有runloop處理完就退出九杂,這里為什么會(huì)這樣呢,我們可以看看runloop的源代碼:
/// RunLoop的實(shí)現(xiàn)
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根據(jù)modeName找到對(duì)應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里沒(méi)有source/timer/observer, 直接返回宣蠕。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即將進(jìn)入 loop例隆。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 內(nèi)部函數(shù),進(jìn)入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)抢蚀。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)镀层。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài)皿曲,直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息唱逢。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 6.通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息屋休。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒惶我。
/// ? 一個(gè)基于 port 的Source 的事件。
/// ? 一個(gè) Timer 到時(shí)間了
/// ? RunLoop 自身的超時(shí)時(shí)間到了
/// ? 被其他什么調(diào)用者手動(dòng)喚醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了博投。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 9.收到消息绸贡,處理消息。
handle_msg:
/// 10.1 如果一個(gè) Timer 到時(shí)間了毅哗,觸發(fā)這個(gè)Timer的回調(diào)听怕。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 10.2 如果有dispatch到main_queue的block,執(zhí)行block虑绵。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 10.3 如果一個(gè) Source1 (基于port) 發(fā)出事件了尿瞭,處理這個(gè)事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 執(zhí)行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 進(jìn)入loop時(shí)參數(shù)說(shuō)處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部調(diào)用者強(qiáng)制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一個(gè)都沒(méi)有了
retVal = kCFRunLoopRunFinished;
}
/// 如果沒(méi)超時(shí)翅睛,mode里沒(méi)空声搁,loop也沒(méi)被停止黑竞,那繼續(xù)loop。
} while (retVal == 0);
}
/// 11. 通知 Observers: RunLoop 即將退出疏旨。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
代碼一長(zhǎng)串很魂,但是標(biāo)了注釋,應(yīng)該大致能看明白檐涝,大概講一下:
函數(shù)的主體是一個(gè)do, while循環(huán)
遏匆,用一個(gè)變量retVal,來(lái)控制循環(huán)的執(zhí)行
谁榜。默認(rèn)為0幅聘,無(wú)限循環(huán)
。
剛進(jìn)入循環(huán)1窃植,2帝蒿,3,4巷怜,5在做一件事陵叽,就是檢查是否有事件需要處理,如果有的話丛版,直接跳到9去處理事件。
處理完事件之后偏序,到第10行页畦,會(huì)去判斷4種是否應(yīng)該跳出循環(huán)的情況,給出變量retVal賦一個(gè)不為0的值研儒,來(lái)跳出循環(huán)豫缨。
如果走到6,則說(shuō)明沒(méi)有事情做端朵,那么runloop就睡眠了好芭,停在第7行,這一行類似sync這樣的同步機(jī)制(其實(shí)不是冲呢,只是舉個(gè)例子舍败。。)敬拓,把程序阻塞在這一行邻薯,直到有消息返回值,才繼續(xù)往下進(jìn)行乘凸。這一阻塞操作是系統(tǒng)內(nèi)核掛起來(lái)的厕诡,阻塞了當(dāng)前的線程,當(dāng)有消息返回時(shí)营勤,因?yàn)楫?dāng)前線程是被阻塞的灵嫌,系統(tǒng)內(nèi)核會(huì)再開(kāi)辟一條新的線程去返回這個(gè)消息壹罚。然后程序繼續(xù)往下進(jìn)行。
走到第8寿羞、9猖凛,通知Observers,然后處理事件稠曼。
到10形病,去判斷是否退出循環(huán)的條件,如果滿足條件退出循環(huán)霞幅,runloop結(jié)束漠吻。反之,又從新開(kāi)始循環(huán)司恳,從2開(kāi)始途乃。
這就是一個(gè)完整的runloop處理事件的流程。
回到上述的例子這種模式下的runloop:
- (BOOL) runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
我們讓線程執(zhí)行了一個(gè)事件扔傅,結(jié)果執(zhí)行完耍共,runloop就退出了,原因是這樣的:
if (sourceHandledThisLoop && stopAfterHandle) {
/// 進(jìn)入loop時(shí)參數(shù)處理完畢事件就返回
retVal = kCFRunLoopHandledSource;
}
這種形式開(kāi)啟的runloop猎塞, stopAfterHandle這個(gè)參數(shù)為YES试读,而sourceHandledThisLoop這個(gè)參數(shù)在如下代碼中被賦值為YES:
/// 10.3 如果一個(gè)Source1(基于port)發(fā)出了事件,處理這個(gè)事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
所以在這里我們觸發(fā)了事件之后荠耽,runloop被退出了钩骇,這個(gè)時(shí)候我們也明白了為什么timer并不會(huì)導(dǎo)致runloop的退出。
接下來(lái)我們分析一下Core Foundation中運(yùn)行的runloop的接口:
/// 運(yùn)行CFRunLoopRef
void CFRunLoopRun()
/// 運(yùn)行CFRunLoopRef: 參數(shù)為運(yùn)行模式铝量、時(shí)間和是否在處理Input Source后退出標(biāo)示倘屹,返回值是exit原因
SInt32 CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);
/// 停止運(yùn)行 CFRunLoopRef
void CFRunLoopStop(CFRunLoopRef rl);
/// 喚醒 CFRunLoopRef
void CFRunLoopWakeUp(CFRunLoopRef rl);
一
void CFRunLoopRun();
運(yùn)行在默認(rèn)的kCFRunLoopDefaultMode模式下,直到CFRunLoopStop接口調(diào)用停止這個(gè)RunLoop慢叨,或者RunLoop的所有事件源被刪除纽匙。
NSRunLoop是基于CFRunLoop來(lái)封裝的,NSRunLoop是線程不安全的
拍谐,而CFRunLoop是線程安全的
烛缔。
在這里我們可以看到和上面NSRunLoop有一個(gè)直觀的區(qū)別是:CFRunLoop能直接停止掉所有的CFRunLoop運(yùn)行起來(lái)的runloop
,其實(shí)之前講到的:
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
這種方式運(yùn)行起來(lái)的runloop也能用CFRunLoopStop 停止掉的轩拨,原因是它完全是基于下面這種方式封裝的:
SInt32 CFRunLoopRunInMode(mode, seconds, returnAfterSourceHandled);
可以看到參數(shù)幾乎一模一樣力穗,前者默認(rèn)returnAfterSourceHandled參數(shù)為YES,當(dāng)觸發(fā)一個(gè)非timer事件后气嫁,runloop就終止了当窗。
這里比較簡(jiǎn)單,就不舉例贅述了寸宵。
二
SInt32 CFRunLoopRunInMode(mode, seconds, returnAfterSourceHandled);
這里有3個(gè)參數(shù)崖面,1個(gè)返回值
其中第一個(gè)參數(shù)
是指RunLoop運(yùn)行的模式
(例如kCFRunLoopDefaultMode或者kCFRunLoopCommonModes)元咙,第二個(gè)參數(shù)
是運(yùn)行事件
,第三個(gè)參數(shù)
是是否在處理事件后讓Run Loop退出返回
巫员,NSRunLoop的第三種開(kāi)啟runloop的方法庶香,綜上所述,我們知道简识,實(shí)際上就是設(shè)置stopAfterHande這個(gè)參數(shù)為YES
關(guān)于返回值赶掖,我們知道調(diào)用runloop運(yùn)行,代碼是停在這一行不返回的七扰,當(dāng)返回的時(shí)候runloop就結(jié)束了奢赂,所以這個(gè)返回值就是runloop結(jié)束的原因,為一個(gè)枚舉值颈走,具體原因如下:
enum {
kCFRunLoopRunFinished = 1, // Run Loop結(jié)束膳灶,沒(méi)有Timer或者其他Input Source
kCFRunLoopRunStopped = 2, // Run Loop被停止,使用CFRunLoopStop停止Run Loop
kCFRunLoopRunTimedOut = 3, // Run Loop超時(shí)
kCFRunLoopRunHandledSource = 4, // Run Loop處理完事件立由,注意Timer事件的觸發(fā)是不會(huì)讓Run Loop退出返回的轧钓,即使CFRunLoopRunInMode的第三個(gè)參數(shù)是YES也不行
}
看到這,我們發(fā)現(xiàn)我們忽略了NSRunLoop第三種開(kāi)啟方式的返回值锐膜。
- (Bool)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
它其實(shí)就是基于CFRunLoopRunInMode封裝的毕箍,它的返回值為一個(gè)Bool值,如果是PerformSelector *事件或者其他Input Source事件觸發(fā)處理后道盏,Run Loop會(huì)退出返回YES而柑, 其他返回NO。
舉個(gè)例子:
- (void)testDemo2
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"starting thread.......");
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(doTimerTask1:) userInfo:remotePort repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//最后一個(gè)參數(shù)捞奕,是否處理完事件返回,結(jié)束runLoop
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 100, YES);
/*
kCFRunLoopRunFinished = 1, //Run Loop結(jié)束,沒(méi)有Timer或者其他Input Source
kCFRunLoopRunStopped = 2, //Run Loop被停止拄轻,使用CFRunLoopStop停止Run Loop
kCFRunLoopRunTimedOut = 3, //Run Loop超時(shí)
kCFRunLoopRunHandledSource = 4 Run Loop處理完事件颅围,注意Timer事件的觸發(fā)是不會(huì)讓Run Loop退出返回的,即使CFRunLoopRunInMode的第三個(gè)參數(shù)是YES也不行
*/
switch (result) {
case kCFRunLoopRunFinished:
NSLog(@"kCFRunLoopRunFinished");
break;
case kCFRunLoopRunStopped:
NSLog(@"kCFRunLoopRunStopped");
case kCFRunLoopRunTimedOut:
NSLog(@"kCFRunLoopRunTimedOut");
case kCFRunLoopRunHandledSource:
NSLog(@"kCFRunLoopRunHandledSource");
default:
break;
}
NSLog(@"end thread.......");
});
}
- (void)doTimerTask1:(NSTimer *)timer
{
count++;
if (count == 2) {
[timer invalidate];
}
NSLog(@"do timer task count:%d",count);
}
2016-11-23 09:19:28.342 TestRunloop3[88598:1971412] starting thread.......
2016-11-23 09:19:29.347 TestRunloop3[88598:1971412] do timer task count:1
2016-11-23 09:19:30.345 TestRunloop3[88598:1971412] do timer task count:2
2016-11-23 09:19:30.348 TestRunloop3[88598:1971412] kCFRunLoopRunFinished
2016-11-23 09:19:30.348 TestRunloop3[88598:1971412] end thread.......
很清楚的可以看到恨搓,當(dāng)timer被置無(wú)效的時(shí)候院促,runloop里面沒(méi)有了任何事件源,所以退出了斧抱,退出原因?yàn)椋簁CFRunLoopRunFinished常拓,線程也就結(jié)束了。
總結(jié)一下:
runloop的運(yùn)行方法一共有5種:包括NSRunLoop的3種辉浦,CFRunLoop的2種弄抬;
而取消的方式一共為3種:
1)移除掉runloop種的所有事件源(timer和source)。
2)設(shè)置一個(gè)超時(shí)時(shí)間宪郊。
3)只要CFRunLoop運(yùn)行起來(lái)就可以用:void CFRunLoopStop(CFRunLoopRef rl); 去停止掂恕。
除此之外用 NSRunLoop也能使用void CFRunLoopStop(CFRunLoopRef rl);
去停止:
[NSRunLoop currentRunLoop] runMode:<#(nonull NSRunLoopMode)#> beforeDate:<#(nonull NSDate)#>
實(shí)現(xiàn)過(guò)程中拖陆,可以根據(jù)需求,我們可以設(shè)置一個(gè)自己的Bool值懊亡,來(lái)控制runloop的開(kāi)始與停止依啰,類似下面這樣:
while(!cancel) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1, YES);
}
每次runloop只運(yùn)行1秒就停止,然后開(kāi)始下一次runloop店枣。
這里最后一個(gè)參數(shù)設(shè)置為YES速警,當(dāng)有非timer事件進(jìn)來(lái),也會(huì)立即開(kāi)始下一次runloop鸯两。
當(dāng)然每次進(jìn)來(lái)我們都可以去修改Mode的值闷旧,這樣我們可以讓runloop每次都運(yùn)行在不同的模式下。
當(dāng)我們不需要runloop的時(shí)候甩卓,可以直接將cancel設(shè)置為YES即可鸠匀。
當(dāng)然,這里只是提供一個(gè)思路逾柿,具體需求缀棍,可以根據(jù)實(shí)際需要進(jìn)行處理。
基于runloop的線程通信
首先明確一個(gè)概念机错,線程間的通信(不僅限于通信爬范,幾乎所有iOS事件都是如此),實(shí)際上是各種輸入源弱匪,觸發(fā)runloop去處理對(duì)應(yīng)的事件青瀑,所以我們先來(lái)講講輸入源:
輸入源異步的發(fā)送消息給你的線程。事件來(lái)源取決于輸入源的種類:
- 基于端口的輸入源和自定義輸入源萧诫。
基于端口的輸入源
監(jiān)聽(tīng)程序相應(yīng)的端口斥难。自定義輸入源
則監(jiān)聽(tīng)自定義的事件源。
當(dāng)你創(chuàng)建輸入源
帘饶,你需要將其分配給run loop中的一個(gè)或多個(gè)模式
哑诊。模式只會(huì)在特定事件影響監(jiān)聽(tīng)的源。大多數(shù)情況下及刻,runloop運(yùn)行在默認(rèn)模式下镀裤,但是你也可以使其運(yùn)行在自定義模式。輸入源生成的消息只在run loop運(yùn)行在其關(guān)聯(lián)的模式下才會(huì)被傳遞
缴饭。
1)基于端口的輸入源:
在runloop中暑劝,被定義名為source1。Cocoa和Core Foundation內(nèi)置支持使用端口相關(guān)的對(duì)象和函數(shù)來(lái)創(chuàng)建的基于端口對(duì)象颗搂,并使用NSPort的方法把端口添加到runloop担猛。端口對(duì)象會(huì)自己處理創(chuàng)建和配置輸入源。
在Core Foundation,你必須人工創(chuàng)建端口和它的run loop源毁习。在兩種情況下智嚷,你都可以使用端口相關(guān)的函數(shù)(CFMachPortRef, CFMessagePortRef, CFSockerRef)來(lái)創(chuàng)建合適的對(duì)象。
這里用Cocoa里的舉個(gè)例子纺且,Cocoa里用來(lái)線程傳值的NSMachPort盏道,它的父類是NSPort。
首先我們看下面:
NSPort *port1 = [[NSPort alloc]init];
NSPort *port2 = [[NSMachPort alloc]init];
NSPort *port3 = [NSPort port];
NSPort *port4 = [NSMachPort port];
我們可以打斷點(diǎn)看到如下:
發(fā)現(xiàn)我們?cè)趺磩?chuàng)建载碌,都返回給我們的是NSMachPort的實(shí)例猜嘱,這應(yīng)該是NSProt內(nèi)部做了一個(gè)消息的轉(zhuǎn)發(fā),這就有點(diǎn)像是一個(gè)抽象類嫁艇,它本身只是定義一些公有屬性和方法朗伶,然后利用集成它的子類去實(shí)現(xiàn)(只是我個(gè)人猜測(cè)。步咪。)
繼續(xù)看我們寫的一個(gè)利用NSMachPort來(lái)線程通信的實(shí)例:
- (void)testDemo3
{
//聲明兩個(gè)端口 隨便怎么寫創(chuàng)建方法论皆,返回的總是一個(gè)NSMachPort實(shí)例
NSMachPort *mainPort = [[NSMachPort alloc]init];
NSPort *threadPort = [NSMachPort port];
//設(shè)置線程的端口的代理回調(diào)為自己
threadPort.delegate = self;
//給主線程runloop加一個(gè)端口
[[NSRunLoop currentRunLoop]addPort:mainPort forMode:NSDefaultRunLoopMode];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//添加一個(gè)Port
[[NSRunLoop currentRunLoop]addPort:threadPort forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
NSString *s1 = @"hello";
NSData *data = [s1 dataUsingEncoding:NSUTF8StringEncoding];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSMutableArray *array = [NSMutableArray arrayWithArray:@[mainPort,data]];
//過(guò)2秒向threadPort發(fā)送一條消息,第一個(gè)參數(shù):發(fā)送時(shí)間猾漫。msgid 消息標(biāo)識(shí)点晴。
//components,發(fā)送消息附帶參數(shù)悯周。reserved:為頭部預(yù)留的字節(jié)數(shù)(從官方文檔上看到的粒督,猜測(cè)可能是類似請(qǐng)求頭的東西...)
[threadPort sendBeforeDate:[NSDate date] msgid:1000 components:array from:mainPort reserved:0];
});
}
//這個(gè)NSMachPort收到消息的回調(diào),注意這個(gè)參數(shù)禽翼,可以先給一個(gè)id屠橄。如果用文檔里的NSPortMessage會(huì)發(fā)現(xiàn)無(wú)法取值
- (void)handlePortMessage:(id)message
{
NSLog(@"收到消息了,線程為:%@",[NSThread currentThread]);
//只能用KVC的方式取值
NSArray *array = [message valueForKeyPath:@"components"];
NSData *data = array[1];
NSString *s1 = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",s1);
// NSMachPort *localPort = [message valueForKeyPath:@"localPort"];
// NSMachPort *remotePort = [message valueForKeyPath:@"remotePort"];
}
2016-11-23 16:50:20.604 TestRunloop3[1322:120162] 收到消息了闰挡,線程為:<NSThread: 0x60800026d700>{number = 3, name = (null)}
2016-11-23 16:50:26.551 TestRunloop3[1322:120162] hello
我們跨越線程锐墙,確實(shí)從主線程往另外一個(gè)線程發(fā)送了消息。
這里我們要注意幾個(gè)點(diǎn):
1)- (void)handlePortMessage:(id)message 這里這個(gè)代理的參數(shù)长酗,從.h中去復(fù)制過(guò)來(lái)的為NSPortMessage類型的一個(gè)對(duì)象溪北,但是我們發(fā)現(xiàn)蘋果只是在.h中@class過(guò)來(lái),我們無(wú)法調(diào)用它的任何方法花枫。所以我們用id聲明刻盐,然后通過(guò)kvc去取它的屬性掏膏。
2)關(guān)于下面這個(gè)傳值類型的問(wèn)題:
NSMutableArray *array = [NSMutableArray arrayWithArray: @[mainPort, data]];
在此我困惑了好一會(huì)兒劳翰。。之前我是往數(shù)組里添加的是String或者其他類型的對(duì)象馒疹,但是發(fā)現(xiàn)參數(shù)傳過(guò)去之后佳簸,變成了nil。于是查了半天資料,依然沒(méi)有結(jié)果生均。于是翻看官方文檔听想,終于在方法描述里看到了(其實(shí)很醒目,然后作者英文水平有限马胧。汉买。)
The components array consists of a series of instances
of some subclass of NSData, and instances of some
subclass of NSPort; since one subclass of NSPort does
not necessarily know how to transport an instance of
another subclass of NSPort (or could do it even if it
knew about the other subclass), all of the instances
of NSPort in the components array and the ‘receivePort’
argument MUST be of the same subclass of NSPort that
receives this message. If multiple DO transports are
being used in the same program, this requires some care.
從這段描述中我們可以看出,這個(gè)傳參數(shù)組里面只能裝兩種類型的數(shù)據(jù)佩脊,一種是NSPort的子類蛙粘,一種是NSData的子類。所有我們?nèi)绻眠@種方式傳值必須得先把數(shù)據(jù)轉(zhuǎn)成NSData類型的才行威彰。
2)Cocoa 執(zhí)行Selector的源
除了基于端口的源出牧,Cocoa定義了自定義輸入源,允許你在任何線程執(zhí)行selector歇盼。它被稱為source0舔痕,和基于端口的源一樣,執(zhí)行selector請(qǐng)求會(huì)在目標(biāo)線程上序列化豹缀,減緩許多在線程上允許多個(gè)方法容易引起的同步問(wèn)題伯复。不像基于源的端口,一個(gè)selector執(zhí)行完后會(huì)自動(dòng)從run loop里面移除
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>]
[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>]
這四個(gè)方法很類似耿眉,一個(gè)是在主線程去掉边翼,一個(gè)可以指定一個(gè)線程。然后一個(gè)帶Mode鸣剪,一個(gè)不帶组底。
大概講一下 waitUntilDone
這個(gè)參數(shù),顧名思義筐骇,就是是否等到結(jié)束债鸡。
1)如果這個(gè)值設(shè)為YES,那么就需要等到這個(gè)方法執(zhí)行完铛纬,線程才能繼續(xù)往下去執(zhí)行厌均。它會(huì)阻塞提交的線程
。
2)如果為NO的話告唆,這個(gè)調(diào)用的方法會(huì)異步的實(shí)行棺弊,不會(huì)阻塞提交的線程
。
3)自定義輸入源
:
為了自定義輸入源擒悬,必須使用Core Foundation里面的CGRunLoopSourceRef類型相關(guān)的函數(shù)來(lái)創(chuàng)建
模她。你可以使用回調(diào)函數(shù)來(lái)配置自定義輸入源。CoreFoundation會(huì)在配置源的不同地方調(diào)用回調(diào)函數(shù)懂牧,處理輸入時(shí)間侈净,在源從runloop移除的時(shí)候清理它。除了定義在事件到達(dá)時(shí)自定義輸入源的行為,你也必須定義消息傳遞機(jī)制畜侦。源的這部分運(yùn)行在單獨(dú)的線程里面元扔,并負(fù)責(zé)在數(shù)據(jù)等待處理的時(shí)候?qū)?shù)據(jù)傳遞給源并通知它處理數(shù)據(jù)。消息傳遞機(jī)制的定義取決于你旋膳,但是最好不要過(guò)于復(fù)雜澎语。
創(chuàng)建自定義的輸入源包括定義下面內(nèi)容:
1.輸入源要處理的信息;
2.使感興趣的客戶端知道如何和輸入源交互的調(diào)度歷程验懊;
3.處理其他任何客戶端發(fā)送請(qǐng)求的歷程咏连;
4.使輸入源失效的取消歷程。
由于創(chuàng)建輸入源來(lái)處理自定義消息鲁森,實(shí)際配置項(xiàng)是靈活的祟滴。調(diào)度歷程,處理歷程和取消歷程都是創(chuàng)建自定義輸入源的關(guān)鍵歷程歌溉。輸入源其他的大部分行為都發(fā)生在這些歷程的外部垄懂。比如,由你決定數(shù)據(jù)傳輸?shù)捷斎朐吹臋C(jī)制痛垛,還有輸入源的其他線程的通信機(jī)制也是由你決定草慧。
下圖中,程序的主線程維護(hù)了一個(gè)輸入源的引用匙头,輸入源所需的自定義命令緩沖區(qū)和輸入源所在的runloop
漫谷。當(dāng)主線程有任務(wù)需要分發(fā)給工作線程時(shí)候,主線程會(huì)給命令緩沖區(qū)發(fā)送命令和必須的信息來(lái)通知工作線程開(kāi)始執(zhí)行任務(wù).(因?yàn)橹骶€程和輸入源所在工作線程都是可以訪問(wèn)命令緩沖區(qū)的蹂析,因此這些方法必須是同步的)舔示,一旦命令發(fā)送出去,主線程會(huì)通知輸入源并喚醒工作線程的runloop电抚。而一收到喚醒命令惕稻,runloop會(huì)調(diào)用輸入源的處理程序,由它來(lái)執(zhí)行命令緩沖區(qū)的響應(yīng)命令蝙叛。
這樣一來(lái)俺祠,我們來(lái)寫一個(gè)實(shí)例來(lái)講講自定義的輸入源(自定義的輸入源,用CF來(lái)實(shí)現(xiàn)):
CFRunLoopRef _runLoopRef;
CFRunLoopSourceRef _source;
CFRunLoopSourceContext _source_context;
首先我們聲明3個(gè)成員變量借帘,這是我們自定義輸入源所需要的3個(gè)參數(shù)蜘渣。具體我們舉例之后再說(shuō):
- (void)testDemo4
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"starting thread.......");
_runLoopRef = CFRunLoopGetCurrent();
//初始化_source_context。
bzero(&_source_context, sizeof(_source_context));
//這里創(chuàng)建了一個(gè)基于事件的源肺然,綁定了一個(gè)函數(shù)
_source_context.perform = fire;
//參數(shù)
_source_context.info = "hello";
//創(chuàng)建一個(gè)source
_source = CFRunLoopSourceCreate(NULL, 0, &_source_context);
//將source添加到當(dāng)前RunLoop中去
CFRunLoopAddSource(_runLoopRef, _source, kCFRunLoopDefaultMode);
//開(kāi)啟runloop 第三個(gè)參數(shù)設(shè)置為YES蔫缸,執(zhí)行完一次事件后返回
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 9999999, YES);
NSLog(@"end thread.......");
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (CFRunLoopIsWaiting(_runLoopRef)) {
NSLog(@"RunLoop 正在等待事件輸入");
//添加輸入事件
CFRunLoopSourceSignal(_source);
//喚醒線程,線程喚醒后發(fā)現(xiàn)由事件需要處理狰挡,于是立即處理事件
CFRunLoopWakeUp(_runLoopRef);
}else {
NSLog(@"RunLoop 正在處理事件");
//添加輸入事件捂龄,當(dāng)前正在處理一個(gè)事件,當(dāng)前事件處理完成后加叁,立即處理當(dāng)前新輸入的事件
CFRunLoopSourceSignal(_source);
}
});
}
//此輸入源需要處理的后臺(tái)事件
static void fire(void* info){
NSLog(@"我現(xiàn)在正在處理后臺(tái)任務(wù)");
printf("%s",info);
}
輸出結(jié)果如下:
2016-11-24 10:42:24.045 TestRunloop3[4683:238183] starting thread.......
2016-11-24 10:42:26.045 TestRunloop3[4683:238082] RunLoop 正在等待事件輸入
2016-11-24 10:42:31.663 TestRunloop3[4683:238183] 我現(xiàn)在正在處理后臺(tái)任務(wù)
hello
2016-11-24 10:42:31.663 TestRunloop3[4683:238183] end thread.......
例中可見(jiàn)我們創(chuàng)建一個(gè)自定義的輸入源倦沧,綁定了一個(gè)函數(shù),一個(gè)參數(shù)它匕,并且這個(gè)輸入源展融,實(shí)現(xiàn)了線程間的通信.
大概說(shuō)一下:
a)CFRunLoopRef _runLoopRef;CF的runLoop。
b)CFRunLoopSourceContext _source_context; 注意到例中用了一個(gè)C函數(shù)bzero(&_source_context, sizeof(_source_context)); 來(lái)初始化豫柬。其實(shí)它的本質(zhì)是一個(gè)結(jié)構(gòu)體:
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
所以bzero(&_source_context, sizeof(_source_context)); 這個(gè)函數(shù)其實(shí)就是把所有的內(nèi)容先置為0告希。
我們?cè)谶@里綁定了兩個(gè)參數(shù)一個(gè)是signal觸發(fā)的函數(shù),一個(gè)是函數(shù)的參數(shù)烧给,至于其他參數(shù)的用途燕偶,可以看看蘋果官方文檔的說(shuō)明:
version
Version number of the structure. Must be 0.
info
An arbitrary pointer to program-defined data, which can be associated with the CFRunLoopSource at creation time. This pointer is passed to all the callbacks defined in the context.
retain
A retain callback for your program-defined info pointer. Can be NULL.
release
A release callback for your program-defined info pointer. Can be NULL.
copyDescription
A copy description callback for your program-defined info pointer. Can be NULL.
equal
An equality test callback for your program-defined info pointer. Can be NULL.
hash
A hash calculation callback for your program-defined info pointer. Can be NULL.
schedule
A scheduling callback for the run loop source. This callback is called when the source is added to a run loop mode. Can be NULL.
cancel
A cancel callback for the run loop source. This callback is called when the source is removed from a run loop mode. Can be NULL.
perform
A perform callback for the run loop source. This callback is called when the source has fired.
c) CFRunLoopSourceRef _source;
這個(gè)是自定義輸入源中最重要的一個(gè)參數(shù)
础嫡。它用來(lái)連接runloop和CFRunLoopSourceContext的一些配置選項(xiàng)
指么,注意我們自定義的輸入源,必須由我們手動(dòng)觸發(fā)
榴鼎。需要先CFRunLoopSourceSignal(_source); 再看當(dāng)前runloop是否在休眠中伯诬,來(lái)看是否需要調(diào)用CFRunLoopWakeUp(_runLoopRef); (一般都是要調(diào)用的)。
4)定時(shí)源:
定時(shí)源在預(yù)設(shè)的時(shí)間點(diǎn)以同步方式傳遞消息巫财。定時(shí)器是線程通知自己做某事的一種方法盗似。
盡管定時(shí)器可以產(chǎn)生基于時(shí)間的通知,但他并不是實(shí)時(shí)機(jī)制平项。和輸入源一樣赫舒,定時(shí)器也和runloop的特點(diǎn)模式相關(guān)。如果定時(shí)器所在的模式當(dāng)前未被runloop監(jiān)視闽瓢,那么定時(shí)器將不會(huì)知道runloop運(yùn)行在響應(yīng)的模式下号阿。類似的,如果定時(shí)器在runloop處理某一事件期間鸳粉,定時(shí)器會(huì)一直等待直到下次runloop開(kāi)始響應(yīng)再處理程序扔涧,如果runloop不運(yùn)行了,那么定時(shí)器也永遠(yuǎn)不啟動(dòng)届谈。
配置定時(shí)源:
Cocoa中可以使用以下NSTimer類方法來(lái)創(chuàng)建一個(gè)定時(shí)器:
[NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> target:<#(nonnull id)#> selector:<#(nonnull SEL)#> userInfo:<#(nullable id)#> repeats:<#(BOOL)#>
[NSTimer timerWithTimeInterval:<#(NSTimeInterval)#> target:<#(nonnull id)#> selector:<#(nonnull SEL)#> userInfo:<#(nullable id)#> repeats:<#(BOOL)#>]
當(dāng)前還有Block枯夜,invocation的形式,就不做贅述了艰山。
第一種timer默認(rèn)是加到NSDefaultRunLoopMode模式下
湖雹。
第二種timer沒(méi)有默認(rèn)值,我們使用的時(shí)候必須調(diào)用 [[NSRunLoop currentRunLoop] addTimer: timer forMode: NSDefaultRunLoopMode]; 去給它指定一個(gè)mode
曙搬。
Core Foundation 創(chuàng)建定時(shí)器
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0, &myCFTimeCallback, &context);
最后用一張runloop運(yùn)行時(shí)的流程圖來(lái)梳理一下我們這些源觸發(fā)的順序
如圖所示摔吏,首先我要明確一個(gè)知識(shí)點(diǎn):runloop跑一圈鸽嫂,只能執(zhí)行一個(gè)事件
。
timer和source0進(jìn)入runloop中征讲,都只是通知Observer我要處理据某,但是還是會(huì)有6、7诗箍、8睡眠喚醒這一步癣籽。但是source1如果有,就會(huì)直接跳到第9步執(zhí)行滤祖。
我們前面也講過(guò)第7步筷狼,這里再提一下。它一直阻塞在這一列匠童,直到:
1)a.source1來(lái)了埂材。b.定時(shí)器Timer啟動(dòng)。c.runloop超時(shí)汤求。d.runloop被顯式喚醒CFRunLoopWakeUp(runloop)(也就是source0)來(lái)了楞遏。
2)這里大家就奇怪了,之前不是說(shuō)source1有的話就直接跳到第9步去執(zhí)行了嗎首昔?寡喝。但是仔細(xì)想想,如果runloop正處于睡眠狀態(tài)下勒奇,這個(gè)時(shí)候source1來(lái)了预鬓,是不是也需要喚醒runloop~
3)至于其他的,應(yīng)該不難理解了赊颠。
Run Loop的Observer
上圖提到了Observer:
Core Foundaton層的接口可以定義一個(gè)Run Loop的觀察格二,在Run Loop進(jìn)入下一個(gè)狀態(tài)時(shí)得到通知:
Run loop進(jìn)入
Run loop處理一個(gè)Timer的時(shí)刻
Run loop處理一個(gè)Input Source的時(shí)刻
Run loop進(jìn)入睡眠的時(shí)刻
Run loop被喚醒的時(shí)刻,但在喚醒它的事件被處理之前
Run loop的終止
Observer的創(chuàng)建以及添加到Run Loop中需要使用Core Foundation的接口:
方法很簡(jiǎn)單如下:
// 創(chuàng)建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
});
// 添加觀察者:監(jiān)聽(tīng)RunLoop的狀態(tài)
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 釋放Observer
CFRelease(observer);
1)方法就是創(chuàng)建一個(gè)observer竣蹦,綁定一個(gè)runloop和模式顶猜,而block回調(diào)就是監(jiān)聽(tīng)到runloop每種狀態(tài)的時(shí)候回觸發(fā)。
2)其中CFRunLoopActivity是一枚舉值痘括,與每種狀態(tài)對(duì)應(yīng):
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 1 // 即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 2 // 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 4 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 32 // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 64
// 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 128 // 即將退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 可以監(jiān)聽(tīng)以上所有狀態(tài)
};