有一定iOS開(kāi)發(fā)經(jīng)驗(yàn)的人可能都聽(tīng)說(shuō)過(guò)RunLoop
网缝。RunLoop
,顧名思義蟋定,就是run loop 粉臊,跑圈的意思。
Apple對(duì)Runloop
是這么解釋的:
The NSRunLoop class declares the programmatic interface to objects that manage input sources. An NSRunLoop object processes input for sources such as mouse and keyboard events from the window system, NSPort objects, and NSConnection objects. An NSRunLoop object also processes NSTimer events.
?
簡(jiǎn)單的驶兜,Runloop
可以理解為一個(gè)事件循環(huán)扼仲,循環(huán)中執(zhí)行不同的代碼,直到進(jìn)入下一次循環(huán)的條件不足為止抄淑!
Runloop
不是線(xiàn)程屠凶,不是GCD,而是一個(gè)對(duì)象肆资,在一個(gè)APP里面不是唯一的矗愧。
下面的這個(gè)圖介紹了代碼執(zhí)行最常見(jiàn)的兩種方式,命令式和event驅(qū)動(dòng)的迅耘。其中一個(gè)是一次執(zhí)行到底贱枣,而另外一個(gè)是反復(fù)不停地進(jìn)行某一個(gè)行為监署,也就是跑圈颤专。
RunLoop
就是事件驅(qū)動(dòng)模型的代表。這種模型被稱(chēng)作 Event Loop钠乏。 Event Loop 在很多系統(tǒng)和框架里都有實(shí)現(xiàn)栖秕,比如 Node.js 的事件處理,比如 Windows 程序的消息循環(huán)晓避,再比如 OSX/iOS 里的 RunLoop
簇捍。實(shí)現(xiàn)這種模型的關(guān)鍵點(diǎn)在于:如何管理事件/消息只壳,如何讓線(xiàn)程在沒(méi)有處理消息時(shí)休眠以避免資源占用、在有消息到來(lái)時(shí)立刻被喚醒暑塑。
所以吼句,RunLoop 實(shí)際上就是一個(gè)對(duì)象,這個(gè)對(duì)象管理了其需要處理的事件和消息事格,并提供了一個(gè)入口函數(shù)來(lái)執(zhí)行上面 Event Loop 的邏輯惕艳。線(xiàn)程執(zhí)行了這個(gè)函數(shù)后,就會(huì)一直處于這個(gè)函數(shù)內(nèi)部 "接受消息->等待->處理" 的循環(huán)中驹愚,直到這個(gè)循環(huán)結(jié)束(比如傳入 quit 的消息)远搪,函數(shù)返回。
OSX/iOS 系統(tǒng)中逢捺,提供了兩個(gè)這樣的對(duì)象:NSRunLoop
和 CFRunLoopRef
谁鳍。
CFRunLoopRef
是在 CoreFoundation
框架內(nèi)的,它提供了純 C 函數(shù)的 API劫瞳,所有這些 API 都是線(xiàn)程安全的倘潜。而NSRunLoop
是基于CFRunLoopRef
的封裝,提供了面向?qū)ο蟮?API柠新,但是這些 API 不是線(xiàn)程安全的窍荧。CoreFoundation
和Foundation
對(duì)象在A(yíng)RC中處理也是不一樣的。所以使用RunLoop
的時(shí)候一定要小心恨憎。
Apple 在RunLoop
的介紹里還特別強(qiáng)調(diào)了下:
The NSRunLoop class is generally not considered to be thread-safe and its methods should only be called within the context of the current thread. You should never try to call the methods of an NSRunLoop object running in a different thread, as doing so might cause unexpected results.
雖然我們無(wú)法創(chuàng)建RunLoop蕊退,但是Apple給我們提供了兩個(gè)自動(dòng)獲取的函數(shù):CFRunLoopGetMain()
和 CFRunLoopGetCurrent()
線(xiàn)程和 RunLoop
之間是一一對(duì)應(yīng)的,一個(gè)線(xiàn)程只能有唯一對(duì)應(yīng)的runloop
憔恳,但這個(gè)runloop
里可以嵌套子runloop
瓤荔,然后把他們之間的關(guān)系保存在一個(gè)全局的 Dictionary 里。線(xiàn)程剛創(chuàng)建時(shí)并沒(méi)有 RunLoop
钥组,如果你不主動(dòng)獲取输硝,那它一直都不會(huì)有。RunLoop
的創(chuàng)建是發(fā)生在第一次獲取時(shí)程梦,RunLoop
的銷(xiāo)毀是發(fā)生在線(xiàn)程結(jié)束時(shí)点把。你只能在一個(gè)線(xiàn)程的內(nèi)部獲取其 RunLoop
(主線(xiàn)程除外)
下面介紹一點(diǎn)稍深入點(diǎn)的知識(shí)
先上圖
RunnLoop
有幾個(gè)運(yùn)行狀態(tài)下的Mode
-
kCFRunLoopDefaultMode
: App的默認(rèn) Mode,通常主線(xiàn)程是在這個(gè) Mode 下運(yùn)行的屿附。 -
UITrackingRunLoopMode
: 界面跟蹤 Mode郎逃,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響挺份,提高用戶(hù)體驗(yàn)褒翰。 -
UIInitializationRunLoopMode
: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用。 -
GSEventReceiveRunLoopMode
: 接受系統(tǒng)事件的內(nèi)部 - -
kCFRunLoopCommonModes
: 這是一個(gè)占位的 Mode优训,沒(méi)有實(shí)際作用朵你。
一個(gè) RunLoop 包含若干個(gè) Mode,每個(gè) Mode 又包含若干個(gè) Source/Timer/Observer揣非。每次調(diào)用 RunLoop
的主函數(shù)時(shí)抡医,只能指定其中一個(gè) Mode,這個(gè)Mode被稱(chēng)作 CurrentMode早敬。如果需要切換 Mode魂拦,只能退出 Loop,再重新指定一個(gè) Mode 進(jìn)入搁嗓。這樣做主要是為了分隔開(kāi)不同組的 Source/Timer/Observer芯勘,讓其互不影響。
?
Source/Timer/Observer 被統(tǒng)稱(chēng)為 mode item腺逛,一個(gè) item 可以被同時(shí)加入多個(gè) mode荷愕。但一個(gè) item 被重復(fù)加入同一個(gè) mode 時(shí)是不會(huì)有效果的。如果一個(gè) mode 中一個(gè) item 都沒(méi)有棍矛,則 RunLoop
會(huì)直接退出安疗,不進(jìn)入循環(huán)。
這里還有個(gè)概念叫 CommonModes够委,一個(gè) Mode 可以將自己標(biāo)記為Common荐类。每當(dāng) RunLoop
的內(nèi)容發(fā)生變化時(shí),RunLoop 都會(huì)自動(dòng)將 CommonMode Items 里的 Source/Observer/Timer 同步到有 Common 標(biāo)記的所有Mode里茁帽。
我們?cè)陂_(kāi)發(fā)中經(jīng)常會(huì)用到定時(shí)器玉罐,如果細(xì)心點(diǎn)就會(huì)發(fā)現(xiàn),timer在你滑動(dòng)的時(shí)候就會(huì)被停止潘拨,當(dāng)滑動(dòng)結(jié)束的時(shí)候才會(huì)繼續(xù)吊输。這就是因?yàn)閙ode不同造成的。我們可以把timer也加到滑動(dòng)專(zhuān)用的trackingMode中去铁追,這樣timer就可以在滑動(dòng)的時(shí)候保持繼續(xù)運(yùn)行季蚂!
詳解:
在主線(xiàn)程的 RunLoop
里有兩個(gè)預(yù)置的 Mode:kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
。這兩個(gè) Mode 都已經(jīng)被標(biāo)記為 Common 屬性琅束。DefaultMode 是 App 默認(rèn)狀態(tài)下所處的狀態(tài)扭屁,TrackingRunLoopMode
是追蹤滑動(dòng)時(shí)的狀態(tài)。當(dāng)你創(chuàng)建一個(gè) Timer 并加到 DefaultMode 時(shí)涩禀,Timer 會(huì)得到重復(fù)回調(diào)料滥,但此時(shí)滑動(dòng)一個(gè)滾動(dòng)視圖時(shí),RunLoop
會(huì)將 mode 切換為 TrackingRunLoopMode
埋泵,這時(shí) Timer 就不會(huì)被回調(diào)幔欧,并且也不會(huì)影響到滑動(dòng)操作。如果將這個(gè) Timer 分別加入這兩個(gè) Mode丽声,或者將 Timer 加入到頂層的 RunLoop 的 CommonMode Items 中礁蔗。CommonModeItems 被 RunLoop
自動(dòng)更新到所有具有 Common 屬性的 Mode 里去。這樣就解決了Timer的回調(diào)問(wèn)題雁社。
還有一個(gè)RunLoop
對(duì)象類(lèi)型浴井,叫做CFRunLoopObserverRef
。它就是RunLoop的觀(guān)察者霉撵,每一個(gè)observer都需要指定一個(gè)回調(diào)函數(shù)的指針磺浙,在當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí),觀(guān)察者就能通過(guò)回調(diào)接受到這個(gè)變化徒坡。
RunLoop
的狀態(tài)有這么幾個(gè)
kCFRunLoopBeforeTimers--------------------------------即將處理Timer
kCFRunLoopBeforeSources-------------------------------即將處理Source
kCFRunLoopBeforeWaiting-------------------------------即將進(jìn)入休眠
kCFRunLoopAfterWaiting--------------------------------剛從休眠中喚醒
kCFRunLoopExit----------------------------------------即將退出Loop
有個(gè)很出名的cell自動(dòng)計(jì)算行高的高性能三方框架就是利用這個(gè)做的優(yōu)化撕氧! 利用RunLoop
即將進(jìn)入休眠的間隙去做一些耗時(shí)的運(yùn)算,可以大幅減少數(shù)據(jù)刷新的整體耗時(shí)喇完,提高用戶(hù)體驗(yàn)伦泥!
小Tips:
初學(xué)iOS的時(shí)候,很多人會(huì)有疑問(wèn)锦溪,被標(biāo)記了autorelease
的對(duì)象究竟在什么時(shí)候釋放了不脯?到了RunLoop
這里就有了答案。RunLoop
BeforeWaiting時(shí)刻诊,將這次Loop中產(chǎn)生的autorelease對(duì)象釋放防楷!
最后提供一些資料: