Runloop相關(guān)探索

Runloop 和 線程

CFRunloop中已經(jīng)說明了一個(gè)線程及其runloop的對(duì)應(yīng)關(guān)系,現(xiàn)在以iOS中NSThread的實(shí)際使用來說明runloop在線程中的意義边翼。

在iOS中直接使用NSThread有一下幾種方式,但是歸根到底肖油,當(dāng)一個(gè)線程需要長(zhǎng)時(shí)間的去跟蹤一個(gè)任務(wù)的時(shí)候岂座,這幾種方式做的事情是一樣的陶因,只不過接口名稱和參數(shù)不一樣,感覺是為了使用起來更加方便巷嚣。因?yàn)檫@些接口內(nèi)部都需要依賴runloop去實(shí)現(xiàn)事件的監(jiān)聽喘先,這個(gè)可以通過調(diào)用堆棧證實(shí)。

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait

以上兩個(gè)方法都是NSObject的方法廷粒,可以直接通過一個(gè)對(duì)象來創(chuàng)建一個(gè)線程窘拯。第二個(gè)方法具有更多的靈活性,它可以讓你自己指定線程坝茎,第一個(gè)方法是自己默認(rèn)創(chuàng)建一個(gè)線程涤姊。第二個(gè)方法的最后一個(gè)參數(shù)是指定是否等待aSelector執(zhí)行完畢。

+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;

該方法是NSThread的類方法景东,跟第一個(gè)方法是類似的功能砂轻。

下面通過在子線程發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求,去發(fā)現(xiàn)一些問題斤吐,然后通過runloop去解釋原因搔涝,并推測(cè)API背后的實(shí)現(xiàn)方式。

- (void)viewDidLoad {
 
    [super viewDidLoad];
 
    [self performSelectorInBackground:@selector(multiThread) withObject:nil];
}
- (void)multiThread
 
{
    if (![NSThread isMainThread]) {
        self.request = [[NSMutableURLRequest alloc]
 
                                        initWithURL:[NSURL URLWithString:@"
                                        http://www.baidu.com"]
 
                                        cachePolicy:NSURLCacheStorageNotAllowed
 
                                        timeoutInterval:10];
 
        [self.request setHTTPMethod: @"GET"];
 
        self.connection =[[NSURLConnection alloc] initWithRequest:self.request
 
                                                         delegate:self
 
                                                 startImmediately:YES];
    }
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(
    NSURLResponse *)response{
 
    NSLog(@"network callback");
 
}

運(yùn)行之后和措,可以發(fā)現(xiàn)在子線程中發(fā)起的網(wǎng)絡(luò)請(qǐng)求庄呈,回調(diào)沒有被調(diào)用。大致猜測(cè)可能跟runloop有關(guān)系派阱,也就是子線程的runloop中沒有注冊(cè)網(wǎng)絡(luò)回調(diào)的消息诬留,所以該子線程自己相關(guān)的runloop沒有收到回調(diào)。實(shí)際上- (instancetype)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL) 這個(gè)方法的第三個(gè)參數(shù)的bool值表示是否在創(chuàng)建完NSURLConnection對(duì)象之后立刻發(fā)起請(qǐng)求,一般情況下是YES文兑,什么時(shí)候會(huì)傳NO呢盒刚。

事實(shí)上,對(duì)于以上這種方式創(chuàng)建的線程绿贞,默認(rèn)是沒有生成該線程對(duì)應(yīng)的runloop的因块。也就是說這種情況下,需要自己去創(chuàng)建對(duì)應(yīng)線程的runloop籍铁,并且讓他run起來涡上,去不斷監(jiān)聽各種往runloop里注冊(cè)的消息。但是對(duì)于主線程而言拒名,其對(duì)應(yīng)的runloop會(huì)由系統(tǒng)建立吩愧,并且自己run起來。由于平時(shí)工作在主線程下增显,這些工作大部分情況下不需要人為參與雁佳,所以一到子線程就會(huì)有各種問題。子線程中起timer沒有生效也是相同的原因同云。所以以上函數(shù)第三個(gè)參數(shù)的意思就是甘穿,如果是當(dāng)前線程已經(jīng)runloop跑起來的情況下,傳YES梢杭。除此之外,需要自己創(chuàng)建runloop去run秸滴,再將網(wǎng)絡(luò)請(qǐng)求消息注冊(cè)到runloop中武契。

現(xiàn)在根據(jù)以上分析修改代碼:

self.request = [[NSMutableURLRequest alloc]
 
                                initWithURL:[NSURL URLWithString:@"http://
                                www.baidu.com"]
 
                                cachePolicy:NSURLCacheStorageNotAllowed
 
                                timeoutInterval:10];
 
[self.request setHTTPMethod: @"GET"];
 
self.connection =[[NSURLConnection alloc] initWithRequest:self.request
 
                                                 delegate:self
 
                                         startImmediately:NO];
 
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
 
[runLoop run];
 
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:
NSDefaultRunLoopMode];
 
[self.connection start];

運(yùn)行之后發(fā)現(xiàn)回調(diào)仍然沒有被調(diào)用,其實(shí)在這里卡了很久荡含。后來一次偶然的調(diào)試中發(fā)現(xiàn)咒唆,代碼運(yùn)行到 [runLoop run]; 就沒有然后了。后面的代碼一直就沒有被執(zhí)行释液,現(xiàn)在修改代碼如下:

self.request = [[NSMutableURLRequest alloc]
 
                                initWithURL:[NSURL URLWithString:@"http://
                                www.baidu.com"]
 
                                cachePolicy:NSURLCacheStorageNotAllowed
 
                                timeoutInterval:10];
 
[self.request setHTTPMethod: @"GET"];
 
self.connection =[[NSURLConnection alloc] initWithRequest:self.request
 
                                                 delegate:self
 
                                         startImmediately:NO];
 
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
 
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:
NSDefaultRunLoopMode];
 
[self.connection start];
 
[runLoop run];

然后就發(fā)現(xiàn)網(wǎng)絡(luò)回調(diào)被調(diào)用了全释。

之后分析了一下調(diào)用堆棧:

第一個(gè):在multiThread里面是這樣的:

multiThread.png

第二個(gè):網(wǎng)絡(luò)回調(diào)里面是這樣的:

http://7xqgnx.com1.z0.glb.clouddn.com/Runloop2.png

通過堆棧可以得知误债,這兩個(gè)函數(shù)都是由線程6調(diào)用的浸船,也就是創(chuàng)建的子線程,但是堆棧中的內(nèi)容很不一樣寝蹈。很顯然第二個(gè)是從runloop調(diào)出的李命,并且是Sources0這個(gè)消息調(diào)出的。而第一個(gè)是線程運(yùn)行時(shí)候的初始化方法箫老。所以當(dāng)調(diào)用runlooprun的時(shí)候封字,其實(shí)是線程進(jìn)入自己的runloop去監(jiān)聽時(shí)間了,從此以后,所有的代碼都會(huì)從runloop CALLOUT出來阔籽。所以這種情況下流妻,需要把先把消息注冊(cè)到runloop中,讓runloop跑起來是最后需要做的事情笆制。

以下是開源庫AFNetworking網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn):

- (void)start {
 
    [self.lock lock];
 
    if ([self isCancelled]) {
        [self performSelector:@selector(cancelConnection) onThread:[[self class
        ] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.
        runLoopModes allObjects]];
 
    } else if ([self isReady]) {
        self.state = AFOperationExecutingState;
        [self performSelector:@selector(operationDidStart) onThread:[[self 
        class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[
        self.runLoopModes allObjects]];
 
    }
    [self.lock unlock];
}
+ (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;
}

AFNetworking使用的是- (void)performSelector:(SEL)aSelector onThread:(NSThread*)thr withObject:(id)arg waitUntilDone:(BOOL)wait這個(gè)方法绅这,但是為什么它沒有使用- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg這個(gè)方法呢?

通過斷點(diǎn)项贺,發(fā)現(xiàn)了AFNetwokring網(wǎng)絡(luò)請(qǐng)求中一些函數(shù)的調(diào)用順序:

1.networkRequestThread

2.networkRequestThreadEntryPoint

3.operationDidStart

為什么operationDidStart會(huì)在networkRequestThreadEntryPoint之后調(diào)用君躺?

在networkRequestThreadEntryPoint里主要是生成網(wǎng)絡(luò)線程的runloop并且讓它跑起來,里面的 [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];這主要是為了在沒有任何網(wǎng)絡(luò)請(qǐng)求的時(shí)候讓網(wǎng)絡(luò)線程保持監(jiān)聽狀態(tài)开缎,否則網(wǎng)絡(luò)線程的loop會(huì)直接返回棕叫,之后再調(diào)用網(wǎng)絡(luò)線程請(qǐng)求就沒有意義了。再結(jié)合調(diào)用堆棧奕删,發(fā)現(xiàn)operationDidStart是在runloop callout出來的俺泣,而networkRequestThreadEntryPoint是網(wǎng)絡(luò)線程的入口方法。這跟之前的例子是一樣的完残。所以伏钠,我猜測(cè)- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait這個(gè)方法背后是由主線程將aSelector作為消息注冊(cè)到runloop中時(shí)間發(fā)生在networkRequestThreadEntryPoint方法調(diào)用之前,所以在networkRequestThreadEntryPoint方法中調(diào)用,NSRunLoopcurrentRunLoop的時(shí)候其實(shí)runloop本身應(yīng)該已經(jīng)被創(chuàng)建了谨设。原因是因?yàn)樵谶@個(gè)地方斷點(diǎn) 熟掂,打印runloop對(duì)象可以發(fā)現(xiàn)里面已經(jīng)注冊(cè)了source0的消息,如下截圖:

http://7xqgnx.com1.z0.glb.clouddn.com/Runloop3.png

也就是說父線程在- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait 函數(shù)中將aSelector注冊(cè)成source0扎拣,這是該函數(shù)背后的大致實(shí)現(xiàn)赴肚。通過查閱apple官方文檔,基本屬實(shí)二蓝,如下所示:

http://7xqgnx.com1.z0.glb.clouddn.com/Runloop4.png

通過上面的分析誉券,可以得出使用performSelector方法可以將子線程runloop的初始化實(shí)現(xiàn)在子線程的初始化方法里實(shí)現(xiàn),如果使用performSelectorInBackground

方法刊愚,那么子線程runloop的初始化和業(yè)務(wù)邏輯就會(huì)混到一起踊跟,并且每一次都會(huì)重新初始化。AFNetworking通過一個(gè)靜態(tài)全局的子線程去管理所有的網(wǎng)絡(luò)請(qǐng)求鸥诽,其對(duì)應(yīng)的runloop也只需要初始化一次商玫。

通過以上分析,可以知道如果需要讓一個(gè)子線程去持續(xù)的監(jiān)聽時(shí)間牡借,就需要啟動(dòng)它的runloop并且忘其中注冊(cè)source决帖,timer,oberserver三者之一的消息類型蓖捶。在默認(rèn)情況下子線程的runloop是不會(huì)自己創(chuàng)建和啟動(dòng)的地回。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末刻像,一起剝皮案震驚了整個(gè)濱河市畅买,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌细睡,老刑警劉巖谷羞,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異溜徙,居然都是意外死亡湃缎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蠢壹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嗓违,“玉大人,你說我怎么就攤上這事图贸□寮荆” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵疏日,是天一觀的道長(zhǎng)偿洁。 經(jīng)常有香客問我,道長(zhǎng)沟优,這世上最難降的妖魔是什么涕滋? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮挠阁,結(jié)果婚禮上何吝,老公的妹妹穿的比我還像新娘。我一直安慰自己鹃唯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布瓣喊。 她就那樣靜靜地躺著坡慌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪藻三。 梳的紋絲不亂的頭發(fā)上洪橘,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音棵帽,去河邊找鬼熄求。 笑死,一個(gè)胖子當(dāng)著我的面吹牛逗概,可吹牛的內(nèi)容都是我干的弟晚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼卿城!你這毒婦竟也來了枚钓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤瑟押,失蹤者是張志新(化名)和其女友劉穎搀捷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體多望,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嫩舟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了怀偷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片家厌。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖枢纠,靈堂內(nèi)的尸體忽然破棺而出像街,到底是詐尸還是另有隱情,我是刑警寧澤晋渺,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布镰绎,位于F島的核電站,受9級(jí)特大地震影響木西,放射性物質(zhì)發(fā)生泄漏畴栖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一八千、第九天 我趴在偏房一處隱蔽的房頂上張望吗讶。 院中可真熱鬧,春花似錦恋捆、人聲如沸照皆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膜毁。三九已至,卻和暖如春愤钾,著一層夾襖步出監(jiān)牢的瞬間瘟滨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工能颁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杂瘸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓伙菊,卻偏偏與公主長(zhǎng)得像败玉,于是被迫代替她去往敵國和親敌土。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 在CFRunloop中已經(jīng)說明了一個(gè)線程及其runloop的對(duì)應(yīng)關(guān)系 绒怨,現(xiàn)在以iOS中NSThread的實(shí)際使用來...
    鬧鬼的金礦閱讀 1,522評(píng)論 0 51
  • runtime 和 runloop 作為一個(gè)程序員進(jìn)階是必須的纯赎,也是非常重要的, 在面試過程中是經(jīng)常會(huì)被問到的南蹂, ...
    made_China閱讀 1,202評(píng)論 0 7
  • 開啟線程 分離主線程創(chuàng)建:創(chuàng)建線程后會(huì)自動(dòng)執(zhí)行犬金,但是線程外部不可獲取到該線程對(duì)象detachNewThreadWi...
    Mr_Pt閱讀 1,055評(píng)論 0 1
  • 消息處理之performSelector[爆棧熱門 iOS 問題] performSelector may cau...
    lionsom_lin閱讀 756評(píng)論 0 3
  • 1.OC里用到集合類是什么? 基本類型為:NSArray六剥,NSSet以及NSDictionary 可變類型為:NS...
    輕皺眉頭淺憂思閱讀 1,363評(píng)論 0 3