如果你沒有了解RunLoop的一些基礎(chǔ),建議你看看這2篇博客,對線程贝缙耄活本質(zhì)理解有很大幫助
(溫馨提示:這里是一步一步探究,步驟過程比較多,如嫌棄啰嗦,可直接拿后面封裝的代碼直,2句即可完美使用.)
我們面試中經(jīng)常遇到很多面試官,問我們關(guān)于RunLoop的知識點,可能我們大多數(shù)人了解RunLoop,但在項目中,我們真正用到RunLoop還是比較少的,RunLoop其實應(yīng)用場景還是比較多,比如我們的定時器、線程背常活渺鹦、性能優(yōu)化、監(jiān)控應(yīng)用卡頓.這個博客主要介紹的是'線程庇己活'
現(xiàn)在的網(wǎng)絡(luò)請求基本都是用的AFNetworking這個框架,相信大家對它很了解,而它里面就是用RunLoop來控制子線程的生命周期.它會讓子線程一直存在內(nèi)存中不釋放,這種好處就是對于我們經(jīng)常去子線程做事情的話,我們就不必一直去創(chuàng)建-銷毀-創(chuàng)建-銷毀,這樣能很大的提高性能,好處也是多多.
接下來我們來看看怎么能做到控制一個線程的生命周期(也就是線程币缡活),想讓它活多久就多久,想讓它什么時候銷毀就什么時候銷毀.
還是老樣子,由簡單到復(fù)雜,我們創(chuàng)建一個線程,這個用NSThread為例子(也可以用PThread,Operation,GCD都可以)
為了能看到線程是什么時候銷毀的,我們可以定一個MyThread,繼承NSThread,
@interface?MyThread : NSThread
并在.m文件,主要監(jiān)測線程啥時候銷毀.
-(void)dealloc{
? ? NSLog(@"%s",__func__);
}
這樣我們就能很清楚的看到MyThread啥時候銷毀:
很清楚,上面的代碼執(zhí)行完NSThread就掛了.所以我們在開發(fā)中如果有需要經(jīng)常在子線程干事情,是不是就是希望這個線程能一直不銷毀,這樣就避免了一直創(chuàng)建-銷毀-創(chuàng)建-銷毀.
那我們怎么處理?直接在子線程加一個runloop,因為我們知道在獲取runloop的時候,就會創(chuàng)建runloop
還是沒能成功,如果你有細(xì)看我之前的2篇博客,你會注意到有一點:
如果Mode里面沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出
所以我們只要在runloop里面加上任何一個就能保證線程不退出,那我們立刻試試:
? 因為是子線程,我們只用NSDefaultRunLoopMode就行了,不用考慮另一個模式
我們再想想,run方法只是達(dá)到了線程苯汗活的功能,真正要執(zhí)行的應(yīng)該還是在這個線程上去執(zhí)行另外的操作.所以我完整的寫一個線程保活的例子,如下:
上面就是一個簡單的線程保活的例子,其實這個是存在一些弊端的,那我們繼續(xù)探究一下.
'線程闭诳В活'存在的問題再探究
為了代碼簡潔易懂,我換一個block創(chuàng)建,跟上面的效果是一模一樣,便于理解,我先是創(chuàng)建2個控制器的跳轉(zhuǎn),因為我想看看,控制器銷毀的時候,線程會不會也跟著銷毀.請看下面的代碼:
看控制臺很明顯,此時并沒有達(dá)到我們想要的結(jié)果,雖然線程確實一直存在,能一直幫我做事情(控制臺可以一直輸出test),但是我們發(fā)現(xiàn)控制器銷毀了,而控制器上的線程卻沒有跟著銷毀!我們看不到控制臺有輸出.那是怎么回事呢?難道出現(xiàn)循環(huán)引用?很明顯不是!
難道是self.Mythread沒有清空?你把self.MyThread = nil;寫在VC的dealloc里面,你會發(fā)現(xiàn),線程依然不會銷毀!
原因是線程一直沒有執(zhí)行完,它一直卡在[[NSRunLoop currentRunLoop] run]這段代碼,所以NSLog(@"---- end ----")也一直沒有執(zhí)行,線程都沒有結(jié)束,所以它不會銷毀.所以如果你想要一個全局的線程,任何頁面都可以調(diào)用,永遠(yuǎn)不用銷毀的話,那這個線程就能達(dá)到這個效果.現(xiàn)在我們想要的效果肯定是在當(dāng)前頁面存在,這個VC銷毀的時候,線程也會銷毀,我們想控制這個線程的生命周期,想讓它銷毀它就銷毀.那我們繼續(xù)看看怎么處理.
我們知道線程執(zhí)行結(jié)束,線程就會銷毀,只要執(zhí)行NSLog(@"---- end ----"),就能讓線程銷毀,所以我們明顯的思路就是在VC的dealloc里面停止self.MyThread線程的RunLoop.請看下面的代碼.
線程依舊沒有銷毀.
有可能有的人會認(rèn)為,這很有可能是執(zhí)行dealloc說明VC快銷毀了,你在快銷毀的VC中執(zhí)行stop方法,是不是來不及呢?那這樣,我用按鈕執(zhí)行stop事件.請看下圖
此時發(fā)現(xiàn),線程依舊沒有銷毀(log中沒有輸出線程的dealloc).
[[NSRunLoop currentRunLoop] run]無限循環(huán);
其實是這個[[NSRunLoop currentRunLoop] run];這個原因?qū)е碌?我們先看官方的解釋,對于這個方法.
看這個上面紅色的翻譯大致意思是:這個方法是無限循環(huán)的執(zhí)行runMode:beforeDate,它是很有效的處理一個無限循環(huán)的.大概類似
white(1)
{runMode:beforeDate}
所以我們大概知道,這個方法是無限循環(huán)也就是關(guān)不了,可以理解為死循環(huán).而我們執(zhí)行的CFRunLoopStop(CFRunLoopGetCurrent()),其實只是停止它無限循環(huán)中的一次runMode:beforeDate,因為它會不斷創(chuàng)建,所以無法全部停止,所以我們很自然想到用runMode:beforeDate這個方法來嘗試解決.
看控制臺的輸出,我們確實是完成了想用它就用它,不用它就讓它銷毀的操作,也完成了控制器銷毀時候,線程也會銷毀.(Stop是一個BOOL值,[NSDate distantFuture]是一個很大的值.)
但是上面的還是不夠完美,我們看看同樣的代碼,假如我們其他操作會不會出現(xiàn)什么問題,比如我們很可能不會點擊停止,直接點返回,此時我們也想銷毀線程,所以我們想著在VC的dealloc里面調(diào)用stop方法試試,請看下面:
看上面的操作,此時應(yīng)該控制器銷毀,而線程依舊還是沒有銷毀
為什么點擊調(diào)用stop就可以使線程銷毀,而在dealloc里面調(diào)用stop就不能使線程銷毀?
我們當(dāng)前這個寫法先看一個注意點:waitUntilDone這個傳值如果傳NO有時候可能導(dǎo)致崩潰,原因如下:
所以上面的參數(shù)我改成YES.
而線程不銷毀的原因是這樣:
再執(zhí)行到這邊的時候,while()里面的條件還是一直是YES,導(dǎo)致還是會一直重復(fù)執(zhí)行里面代碼,所以runloop還是會一直運行.所以我們把條件改一下即可:?while(!weakSelf.Stop&& weakSelf) 把條件改成這個即可(如果這里用__strong處理可能產(chǎn)生循環(huán)引用).
看運行結(jié)果,這種操作完美解決!
我們再驗證一下之前點擊停止的那種模式有沒有影響!你會發(fā)現(xiàn)又崩了!如下:
原因很簡單:點擊停止已經(jīng)調(diào)用了stop,RunLoop已經(jīng)結(jié)束了,它已經(jīng)不能做事情了,只是沒有銷毀.你返回的時候dealloc又調(diào)用了stop,又讓它去工作,肯定會出問題的.所以我們只要加一個判斷即可:
這下真的完美解決了,如論怎么操作,線程和控制器都會銷毀!我們看一下成果:
這里我們發(fā)現(xiàn)很多步驟才出來結(jié)果,而且是感覺還有點麻煩,那我們直接封裝即可:
線程的封裝:
封裝用起來就非常容易了.先看執(zhí)行調(diào)用代碼,再看封裝
封裝以后就剩這3句,初始化,調(diào)用做事,停止,代碼很少,很好用,請看效果:
完美解決了這個問題.請看下面封裝代碼:(就是把我們之前寫的,封裝起來了):
到這里,我們基本把要說的都說了,該封裝的已經(jīng)封裝了,有需要可以直接拿去調(diào)用!
因為理論上控制器銷毀了,線程也會跟著銷毀,所以控制的dealloc里面應(yīng)該是不用調(diào)用stop,按照這個思路我們直接在GDThread的dealloc里面調(diào)用stop方法即可,那封裝的線程將變得更簡單,只有2步操作即可.,初始化,調(diào)用做事!
2句代碼完成保活!
拓展--C語言的封裝
有上面的封裝其實已經(jīng)可以,用C語言的封裝作為了解一下:
只要換了紅色圈圈的內(nèi)容即可,其他的都和原來一樣,這里也是可以直接使用.