今天我們來(lái)學(xué)習(xí)下iOS中一個(gè)較為重要的核心--RunLoop裹粤。其實(shí)我們對(duì)RunLoop既熟悉又陌生终蒂。熟悉是因?yàn)槲覀冊(cè)陂_發(fā)中時(shí)不時(shí)的都會(huì)用到它蜂林,陌生是因?yàn)樗^為底層遥诉,我們對(duì)它的了解不是多拇泣。今天我們就一起來(lái)揭開RunLoop神秘的面紗,對(duì)他進(jìn)行一個(gè)較為簡(jiǎn)單的介紹矮锈。通過(guò)我的簡(jiǎn)單介紹霉翔,如果大家能對(duì)它有一定的了解和認(rèn)識(shí),那無(wú)論是在平時(shí)的工作中苞笨,還是在以后的面試中债朵,它都是我們能拿上臺(tái)面的一個(gè)利器。好瀑凝,廢話不多說(shuō)序芦,進(jìn)入正題吧。
1.RunLoop的簡(jiǎn)單介紹
1.1 RunLoop的基本概念
那么到底是什么RunLoop呢粤咪,從字面意思上可以得知谚中,他就是“跑圈”,翻譯的雅一點(diǎn)就是“運(yùn)行的循環(huán)”寥枝。在iOS SDK中宪塔,RunLoop實(shí)際上也是一個(gè)對(duì)象,這個(gè)對(duì)象在循環(huán)的處理程序在運(yùn)行過(guò)程中發(fā)出各種事件囊拜,例如某筐,用戶點(diǎn)擊了屏幕(TouchEvent
),UI界面的刷新事件冠跷,定時(shí)器事件(Timer
)南誊,Selector
事件等等。當(dāng)我們將我們的程序退出到后臺(tái)蜜托,注意只是退出到后臺(tái)弟疆,并沒(méi)有terminate,這時(shí)RunLoop不會(huì)處理任何事件盗冷,此時(shí)為了節(jié)省CPU的資源怠苔,RunLoop會(huì)自動(dòng)進(jìn)入休眠模式。
1.2 RunLoop和線程的關(guān)系
當(dāng)我們談?wù)摰絉unLoop仪糖,其實(shí)我們就會(huì)提到與之息息相關(guān)的東西柑司,線程。我們知道锅劝,線程的作用是用來(lái)執(zhí)行一個(gè)或者多個(gè)特定任務(wù)的攒驰,一般情況下,某個(gè)線程的任務(wù)執(zhí)行完畢故爵,他就return掉玻粪。那么,我們?yōu)榱俗尵€程能夠不斷執(zhí)行我們指派的,或者系統(tǒng)分配任務(wù)劲室,讓其任務(wù)執(zhí)行完后并不return伦仍,這時(shí)候我們就用到了RunLoop。
一個(gè)線程對(duì)應(yīng)唯一一個(gè)RunLoop對(duì)象很洋,這點(diǎn)很重要充蓝。但是往往,RunLoop并不能保證我們線程安全喉磁,因?yàn)槲覀冎荒茉诋?dāng)前線程中操作與之對(duì)應(yīng)的RunLoop對(duì)象谓苟,而不能在當(dāng)前線程中去操作其他線程的RunLoop對(duì)象。子線程的RunLoop對(duì)象协怒,需要我們手動(dòng)去創(chuàng)建和維護(hù)涝焙,當(dāng)線程結(jié)束時(shí),它也就銷毀了孕暇。這里我們就會(huì)想到仑撞,那么主線程mainThread
的RunLoop,是誰(shuí)創(chuàng)建的呢芭商?其實(shí)這個(gè)問(wèn)題有點(diǎn)白癡 : ]派草,當(dāng)然是系統(tǒng)自動(dòng)創(chuàng)建的。
1.3 主線程的RunLoop
當(dāng)我們創(chuàng)建一個(gè)新工程的時(shí)候铛楣,我們找他的main.m
近迁,我們可以看到他的代碼很簡(jiǎn)單,就一個(gè)main方法簸州,代碼如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
這個(gè)方法就是我們程序的入口鉴竭,在這個(gè)方法中,首先標(biāo)注了一個(gè)自動(dòng)釋放池autoreleasepool
岸浑,然后再返回一個(gè)系統(tǒng)函數(shù)UIApplicationMain
執(zhí)行結(jié)果搏存,其實(shí)這個(gè)UIApplicationMain
函數(shù)就是為我們的程序創(chuàng)建了一個(gè)主線程的RunLoop,只要我們的程序不crash掉矢洲,那他就會(huì)一直運(yùn)行循環(huán)璧眠,直到我們將程序退出到后臺(tái),或者terminate掉读虏,它才會(huì)被掛起或是被銷毀掉责静。
我們?cè)賮?lái)看看,官方文檔上是如何圖解RunLoop的盖桥,如下圖所示:
從上圖中我們可以看出灾螃,RunLoop實(shí)際就是一個(gè)循環(huán),它會(huì)一直監(jiān)聽(tīng)輸入源
InputSources
和定時(shí)源TimerSources
揩徊,一旦接收到事件腰鬼,它便會(huì)對(duì)其進(jìn)行處理嵌赠,如果長(zhǎng)時(shí)間沒(méi)有檢測(cè)到事件,那么它會(huì)自動(dòng)進(jìn)入休眠狀態(tài)熄赡。
2.RunLoop相關(guān)類介紹
我們要想對(duì)RunLoop有跟進(jìn)一步的理解姜挺,我則需要對(duì)CoreFoundation
框架下的與RunLoop相關(guān)的5個(gè)類,他們分別是:
-
CFRunLoopRef
:RunLoop對(duì)象 -
CFRunLoopMode
:RunLoop的運(yùn)行模式 -
CFRunLoopSourceRef
:RunLoop監(jiān)聽(tīng)的輸入源(事件源) -
CFRunLoopTimerRef
:RunLoop監(jiān)聽(tīng)的定時(shí)源(Timer源) -
CFRunLoopObserverRef
:觀察者本谜,監(jiān)聽(tīng)RunLoop狀態(tài)的變化
接下來(lái)我們就來(lái)詳細(xì)的說(shuō)下上面的這5個(gè)類的具體內(nèi)容和他們之間的關(guān)系初家。
首先我們來(lái)看下5個(gè)類的關(guān)系偎窘,如下圖所示乌助,
我們來(lái)簡(jiǎn)單解釋下他們之間的關(guān)系,一個(gè)
CFRunLoopRef
對(duì)象包含若干個(gè)CFRunLoopMode
運(yùn)行模式陌知,每個(gè)運(yùn)行模式又包含若干個(gè)CFRunLoopSourceRef
輸入源他托、CFRunLoopTimerRef
定時(shí)源、CFRunLoopObserverRef
觀察者仆葡。這里需要注意的是赏参,雖然一個(gè)RunLoopRef
對(duì)象可以包含若干個(gè)CFRunLoopMode
運(yùn)行模式,但是該RunLoopRef
對(duì)象當(dāng)前運(yùn)行的模式只能是指定的它包含的若干個(gè)CFRunLoopMode
運(yùn)行模式中的一個(gè)沿盅,那么這個(gè)被指定的運(yùn)行模式就叫做“當(dāng)前運(yùn)行模式”CurrentMode
把篓。但是CurrentMode
是可以進(jìn)行切換的,如果需要切換運(yùn)行模式腰涧,則需要退出當(dāng)前的Loop韧掩,然后重新指定CFRunLoopMode
。這樣做的目的其實(shí)也很容明白窖铡,因?yàn)槊恳粋€(gè)CFRunLoopMode
包含若干個(gè)CFRunLoopSourceRef
疗锐、CFRunLoopTimerRef
、CFRunLoopObserverRef
费彼,為了能有效的隔離不同CFRunLoopMode
的CFRunLoopSourceRef
滑臊、CFRunLoopTimerRef
罕模、CFRunLoopObserverRef
涧偷,使其不同CFRunLoopSourceRef
互不影響凛驮,所以一個(gè)CFRunLoopRef
只能指定一個(gè)CurrentMode
瓦哎。接下來(lái)摔敛,我們將逐一詳細(xì)的來(lái)介紹與RunLoop密切相關(guān)的這5個(gè)類棵帽。
2.1 CFRunLoopRef
類
CFRunLoopRef是CoreFoundation內(nèi)庫(kù)中RunLoop對(duì)象類饱苟。我們可以以下方式來(lái)獲取CFRunLoopRef對(duì)象腊凶。
// 獲取當(dāng)前線程的CFRunLoopRef
CFRunLoopRef runLoopRef = CFRunLoopGetCurrent();
// 獲取主線程的CFRunLoopRef
CFRunLoopRef mainRunLoopRef = CFRunLoopGetMain();
NSRunLoop是Foundation內(nèi)庫(kù)中RunLoop的對(duì)象芙粱。我們同樣可以使用以下方法來(lái)回去NSRunLoop對(duì)象
// 獲取當(dāng)前RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 獲取主線程的RunLoop
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
2.2 CFRunLoopMode
類
iOS系統(tǒng)第一了很多種運(yùn)行模式祭玉,下面我們一一簡(jiǎn)單的介紹:
-
kCFRunLoopDefaultMode
:App默認(rèn)運(yùn)行模式,通常我們的主線程的當(dāng)前模式就是這個(gè)模式春畔。 -
UITrackingRunLoopMode
:跟蹤用戶交互事件(用于ScrollView追蹤觸摸滑動(dòng)脱货,保證界面滑動(dòng)時(shí)不受其他Mode的影響)岛都。 -
UIInitializationRunLoopMode
:當(dāng)App剛啟動(dòng)的時(shí)候,系統(tǒng)將進(jìn)入這個(gè)模式振峻,啟動(dòng)完成后將切換到另外的運(yùn)行模式臼疫。 -
GSEventReceiveRunLoopMode
:接受系統(tǒng)內(nèi)部事件,通常我們?cè)匍_發(fā)的時(shí)候很少用這個(gè)運(yùn)行模式扣孟。 -
kCFRunLoopCommonModes
:偽模式烫堤,不是一種真正的運(yùn)行模式,我們?cè)诤竺娴膬?nèi)容中會(huì)介紹到這個(gè)運(yùn)行模式凤价。
以上提及到的5種運(yùn)行模式中鸽斟,我們?cè)陂_發(fā)過(guò)中常用到的是kCFRunLoopDefaultMode
、UITrackingRunLoopMode
利诺、kCFRunLoopCommonModes
富蓄,那么下面就來(lái)講講這三種運(yùn)行模式的具體用法。
2.3 CFRunLoopTimerRef (Timer事件源)
CFRunLoopTimerRef
是我們上文提及到的定時(shí)源慢逾,他是RunLoop監(jiān)聽(tīng)的事件源之一立倍,在RunLoop相關(guān)類的關(guān)系圖中我們提過(guò)它。我們可以簡(jiǎn)單的將其理解為基于時(shí)間的觸發(fā)器侣滩。我們也可以將其理解我們開發(fā)時(shí)常用的NSTimer口注。
接下來(lái)我們舉一個(gè)簡(jiǎn)單的例子來(lái)演示一下CFRunLoopMode
和CFRunLoopTimerRef
相結(jié)合的簡(jiǎn)單用法。
1.首先我們?cè)賃IViewController.view上添加一個(gè)scrollView, 在scrollView上我們?cè)偬砑右粋€(gè)Label, 然后我們?cè)賾屑虞d一個(gè)定時(shí)器君珠, 然后我們啟動(dòng)定時(shí)器寝志,定時(shí)器將每隔1秒修改Label.text,代碼如下:
- (void)viewDidLoad {
[self.view addSubview:self.scrollView];
[self.scrollView addSubview: self.label];
[self.timer fire];
}
- (void)on_timer {
_num += 1;
self.label.text = [NSString stringWithFormat:@"%zd", _num];
}
// 懶加載一個(gè)定時(shí)器
- (NSTimer *)timer {
if (!_timer) {
_timer = ({
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(on_timer) userInfo:nil repeats:YES];
timer;
});
}
return _timer;
}
我們運(yùn)行我們的程序葛躏,我們不做任何操作澈段,我們可以看到我們的label.text會(huì)每隔一秒鐘就變化一次。如果此時(shí)我們滑動(dòng)我們的scrollView舰攒,我們就會(huì)發(fā)現(xiàn)label.text將不會(huì)再發(fā)生改變败富,當(dāng)我們停止滑動(dòng),手指離開屏幕的時(shí)候摩窃,label.text又開始繼續(xù)發(fā)生改變兽叮。那么到底是什么原因到導(dǎo)致這樣的結(jié)果呢?
當(dāng)我們?cè)俪跏蓟疶imer的時(shí)候猾愿,沒(méi)有手動(dòng)將Timer注入到某種運(yùn)行模式下鹦聪,此時(shí)系統(tǒng)會(huì)默認(rèn)將其注入到NSDefaultRunLoopMode
,所以我們運(yùn)行程序蒂秘,且不做任何操作的時(shí)候泽本,此時(shí)RunLoop是當(dāng)currentMode是NSDefaultRunLoopMode
,Timer正常工作姻僧。當(dāng)我們滑動(dòng)scrollView的時(shí)候规丽,此時(shí)系統(tǒng)會(huì)將currentMode切換到UITrackingRunLoopMode
蒲牧,我們的Timer并沒(méi)有注入到這個(gè)模式下,所以Timer失效赌莺。當(dāng)我們的滑動(dòng)結(jié)束冰抢,手指離開屏幕的時(shí)候,currentMode又切換到NSDefaultRunLoopMode
艘狭,此時(shí)Timer又正常工作挎扰。那么我么在初始化Timer的時(shí)候,可以將其注入到指定的運(yùn)行模式下嗎巢音?是可以的遵倦,代碼如下:
// 手動(dòng)的將Timer注入到UITrackingRunLoopMode模式下
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
此時(shí)我們手動(dòng)將Timer注入到UITrackingRunLoopMode
,然后我們運(yùn)行程序港谊,其實(shí)顯而易見(jiàn)骇吭,此時(shí)我們不做任何操作橙弱,Timer是不會(huì)正常工作的歧寺,當(dāng)我們滑動(dòng)scrollView的時(shí)候,Timer開始正常工作棘脐,原因與上同理斜筐。這里我來(lái)問(wèn)個(gè)S13問(wèn)題:), 難倒我們就不能在這兩種模式下讓Timer都能正常工作嗎蛀缝?(這個(gè)問(wèn)題真的S13)顷链。
答案是當(dāng)然可以的,其實(shí)我是為了引出偽模式(kCFRunLoopCommonModes
)屈梁。kCFRunLoopCommonModes
其實(shí)都不能算是一種運(yùn)行模式嗤练,它只是一種標(biāo)記,它可以對(duì)其他運(yùn)行模型進(jìn)行標(biāo)記在讶。說(shuō)道這里煞抬,大家可能都明白了系統(tǒng)的NSDefaultRunLoopMode
和UITrackingRunLoopMode
都是被標(biāo)記上Common modes。所以我們只需要將我們的timer手動(dòng)注入到kCFRunLoopCommonModes
下构哺,那么此時(shí)無(wú)論系統(tǒng)的currentMode是default還是UITracking革答,timer將都能正常工作。修改代碼如下:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
這里我們說(shuō)道了Timer曙强,那么我就順便就說(shuō)說(shuō)這個(gè)timer
NSTimer有兩個(gè)便利構(gòu)造函數(shù)入下:
// 構(gòu)造函數(shù)1
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
// 構(gòu)造函數(shù)2
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
構(gòu)造函數(shù)1残拐,返回的timer對(duì)象會(huì)自動(dòng)注入到NSDefaultRunLoopMode,其實(shí)相當(dāng)于構(gòu)造函數(shù)1碟嘴,再手動(dòng)注入到運(yùn)行模式中溪食,例如:
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(...) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
2.4 CFRunLoopSourceRef (Input事件源)
所有的事件源有兩種分類方法,我們來(lái)具體看看他哪兩種分類:
按照官方文檔來(lái)分類(和前面我貼出的官方RunLoop模型圖里一樣):
Port-Based Sources :基于端口事件
Custom Input Sources:用戶自定義事件按照函數(shù)調(diào)用棧來(lái)分類:
Source0:非基于端口事件
Source1:基于端口娜扇,通過(guò)內(nèi)核和其他線程通信错沃、接收边败、分發(fā)系統(tǒng)事件
其實(shí)根本一點(diǎn)講,這兩種分類是沒(méi)有區(qū)別的捎废,只不過(guò)呢笑窜,第一種是根據(jù)官方給出的理論來(lái)進(jìn)行分類的。第二是我們?cè)趯?shí)際應(yīng)用中通過(guò)函數(shù)的調(diào)用來(lái)進(jìn)行分類的登疗。
2.5 CFRunLoopObserverRef (I觀察者類)
RunLoop的狀態(tài)會(huì)根據(jù)用戶的操作以及程序狀態(tài)的變化而發(fā)生改變排截,通常我們會(huì)去監(jiān)聽(tīng)RunLoop的實(shí)時(shí)變化,此時(shí)我們就會(huì)用到一個(gè)觀察者類CFRunLoopObserverRef
辐益。RunLoop的狀態(tài)集是以一個(gè)枚舉類型來(lái)表示断傲,入下面代碼所示:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入Loop:1
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer:2
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source:4
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠:32
kCFRunLoopAfterWaiting = (1UL << 6), // 即將從休眠中喚醒:64
kCFRunLoopExit = (1UL << 7), // 即將從Loop中退出:128
kCFRunLoopAllActivities = 0x0FFFFFFFU // 監(jiān)聽(tīng)全部狀態(tài)改變
};
下面我們用個(gè)小小的例子來(lái)觀察下,RunLoop的狀態(tài)到底是怎么變化的智政,以及我們?nèi)绾问褂?code>CFRunLoopObserverRef來(lái)監(jiān)聽(tīng)RunLoop的狀態(tài)變化认罩。
首先我們?cè)趘iewDidLoad中添加如下代碼:
CFRunLoopObserverRef observerRef =
CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),
kCFRunLoopAllActivities,
YES,
0,
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"監(jiān)聽(tīng)到RunLoop發(fā)生改變---%zd",activity);
});
// 2.append Observer to RunLoop
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observerRef, kCFRunLoopDefaultMode);
// 3.release observer
CFRelease(observerRef);
然后我們運(yùn)行我們的程序,然后控制臺(tái)會(huì)打印出一大串信息续捂,這里我只貼出最后部分打印信息:
2018-12-25 10:36:03.293175+0800 TestApp[12836:1611962] 監(jiān)聽(tīng)到RunLoop發(fā)生改變---2
2018-12-25 10:36:03.293363+0800 TestApp[12836:1611962] 監(jiān)聽(tīng)到RunLoop發(fā)生改變---4
2018-12-25 10:36:03.293517+0800 TestApp[12836:1611962] 監(jiān)聽(tīng)到RunLoop發(fā)生改變---32
2018-12-25 10:37:00.032407+0800 TestApp[12836:1611962] 監(jiān)聽(tīng)到RunLoop發(fā)生改變---64
2018-12-25 10:37:00.036248+0800 TestApp[12836:1611962] 監(jiān)聽(tīng)到RunLoop發(fā)生改變---2
2018-12-25 10:37:00.036335+0800 TestApp[12836:1611962] 監(jiān)聽(tīng)到RunLoop發(fā)生改變---4
2018-12-25 10:37:00.036392+0800 TestApp[12836:1611962] 監(jiān)聽(tīng)到RunLoop發(fā)生改變---32
我們可以看到垦垂,RunLoop的狀態(tài)是在不斷的變化的,他最后的狀態(tài)是32牙瓢,也就是我們上面的枚舉中的kCFRunLoopBeforeWaiting
劫拗,RunLoop即將進(jìn)入休眠。
3.RunLoop的原理介紹
OK矾克,我們上面講解了RunLoop的基本概念页慷,以及與RunLoop相關(guān)的幾個(gè)類的介紹,接下來(lái)我們將具體的來(lái)說(shuō)一說(shuō)RunLoop的原理胁附。它到底是怎么運(yùn)作酒繁,希望通過(guò)下面講解,大家都可以了解RunLoop的內(nèi)部運(yùn)行邏輯控妻。
這里我貼一張我自己畫的圖州袒,簡(jiǎn)單的描述了一下RunLoop的運(yùn)行邏輯,如下圖所示:
上圖是我自己按照自己的理解畫的RunLoop運(yùn)行邏輯圖饼暑,可能對(duì)大家在理解RunLoop的運(yùn)行邏輯的時(shí)候有一點(diǎn)幫助稳析,下面我們來(lái)看看官方文檔對(duì)RunLoop的運(yùn)行邏輯是如何闡述的。
當(dāng)我們運(yùn)行我們的程序的時(shí)候弓叛,所有線程的RunLoop會(huì)同時(shí)被喚醒彰居,并且開始自動(dòng)處理之前未處理完的事件,通知也發(fā)出通知撰筷,通知其相應(yīng)的Observer陈惰。詳細(xì)的順序如下:
- 通知觀察者RunLoop已經(jīng)啟動(dòng)
- 通知觀察者即將要開始的定時(shí)器
- 通知觀察者任何即將啟動(dòng)的非基于端口的源
- 啟動(dòng)任何準(zhǔn)備好的非基于端口的源
- 如果基于端口的源準(zhǔn)備好并處于等待狀態(tài),立即啟動(dòng)毕籽;并進(jìn)入步驟9
- 通知觀察者線程進(jìn)入休眠狀態(tài)
- 將線程置于休眠知道任一下面的事件發(fā)生:
某一事件到達(dá)基于端口的源
定時(shí)器啟動(dòng)
RunLoop設(shè)置的時(shí)間已經(jīng)超時(shí)
RunLoop被顯示喚醒 - 通知觀察者線程將被喚醒
- 處理未處理的事件
如果用戶定義的定時(shí)器啟動(dòng)抬闯,處理定時(shí)器事件并重啟RunLoop井辆。進(jìn)入步驟2
如果輸入源啟動(dòng),傳遞相應(yīng)的消息
如果RunLoop被顯示喚醒而且時(shí)間還沒(méi)超時(shí)溶握,重啟RunLoop杯缺。進(jìn)入步驟2 - 通知觀察者RunLoop結(jié)束。
以上就是官方對(duì)RunLoop的運(yùn)行邏輯給出的說(shuō)明睡榆,我是直接用有道翻譯出來(lái)的萍肆,沒(méi)有去做校驗(yàn),大家可以對(duì)照我上面貼出的圖胀屿,大致看下RunLoop的運(yùn)行邏輯塘揣。
4.RunLoop開發(fā)應(yīng)用
上面說(shuō)一大堆,很多都是概念性東西宿崭,要想真正的掌握RunLoop我們還是得到實(shí)際運(yùn)用中亲铡。通過(guò)在實(shí)際開發(fā)過(guò)程中的實(shí)際應(yīng)用,我們對(duì)RunLoop的認(rèn)識(shí)才能更加深刻葡兑。
4.1 NSTimer的應(yīng)用
NSTimer奖蔓,我們上面的2.3CFRunLoopTimerRef (Timer事件源)中已經(jīng)提到過(guò)有關(guān)NSTimer和RunLoop的關(guān)系,這里呢铁孵,我們就不再重復(fù)了锭硼。
4.2 開啟常駐線程
在我們的實(shí)際開發(fā)過(guò)程中,有時(shí)候可能會(huì)遇到在后臺(tái)頻繁操作的的一些需求蜕劝,例如,文件現(xiàn)在轰异,音樂(lè)播放岖沛,實(shí)時(shí)定位等等,這些操作經(jīng)常會(huì)在子線程做一些耗時(shí)的操作搭独,此時(shí)婴削,我們就可以應(yīng)用RunLoop將這些耗時(shí)的子線程放到我們的常駐線程中去。
具體的操作室牙肝,代碼如下:
1.首先我們開啟一條子線程:
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化線程唉俗,執(zhí)行任務(wù)run1
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
[self.thread start];
}
- (void) run1
{
NSLog(@"----run1-----");
// 添加下邊兩句代碼,就可以開啟RunLoop配椭,之后self.thread就變成了常駐線程虫溜,可隨時(shí)添加任務(wù),并交于RunLoop處理
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
// 如果開啟RunLoop股缸,則不會(huì)執(zhí)行下面這句打印衡楞,因?yàn)镽unLoop開啟了循環(huán)。
NSLog(@"未開啟RunLoop");
}
我們運(yùn)行我們的程序敦姻,控制并沒(méi)有打印 "未開啟RunLoop"瘾境, 說(shuō)明我們開啟了一條常駐線程歧杏,此時(shí)我們?cè)偻€程中添加一些子任務(wù)代碼如下:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 在self.thread的線程中執(zhí)行任務(wù)run2
[self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void) run2
{
NSLog(@"----run2------");
}
此時(shí)我們運(yùn)行我們的程序,每當(dāng)我們點(diǎn)擊我們的屏幕的時(shí)候迷守,控制臺(tái)都會(huì)打印“----run2------”犬绒,這樣我們就實(shí)現(xiàn)了常駐線程的需求。當(dāng)然這里我只是簡(jiǎn)單的給大家演示了一下常駐線程的例子兑凿,那么大家可以根據(jù)自己不同是實(shí)際開發(fā)需求懂更,來(lái)進(jìn)行編碼,并將RunLoop特性發(fā)揮起來(lái)急膀。
以上就是對(duì)RunLoop的基本概念沮协,相關(guān)類,運(yùn)行邏輯卓嫂,以及實(shí)際應(yīng)用的簡(jiǎn)單介紹慷暂,當(dāng)然我個(gè)人是水平也是比較次,有不對(duì)的晨雳,或者不準(zhǔn)確的地方行瑞,希望大家留言指正。