iOS中的CATransaction是干什么的

首先從Image的加載說起

從磁盤加載一張圖片,使用UIImageVIew顯示在屏幕上,需要經(jīng)過以下步驟

(1) 從磁盤拷貝數(shù)據(jù)到內(nèi)核緩沖區(qū)

(2) 從內(nèi)核緩沖區(qū)復(fù)制數(shù)據(jù)到用戶空間

(3) 把圖像數(shù)據(jù)賦值給UIImageView

(4) 如果圖像數(shù)據(jù)為未解碼的PNG/JPG,解碼為位圖數(shù)據(jù)

(5) CATransaction捕獲到UIImageView的layer的圖層樹的變化

(6) Runloop回調(diào)對CATransaction的Observer函數(shù),開始進行圖像渲染

? ? (6.1) 如果數(shù)據(jù)沒有字節(jié)對齊,Core Animation會再拷貝一份數(shù)據(jù),進行字節(jié)對齊

? ? (6.2) GPU處理位圖數(shù)據(jù)并進行渲染


然后我們討論一下CALayer

CALayer 和 UIView 一樣存在著一個層級樹狀結(jié)構(gòu),稱之為圖層樹(Layer Tree)。

直接創(chuàng)建的layer或者通過 view.layer 獲得的layer是用于顯示的圖層樹,稱之為模型樹(Model Tree),模型樹的背后還存在兩份圖層樹的拷貝,一個是呈現(xiàn)樹(Presentation Tree),一個是渲染樹(Render Tree)。呈現(xiàn)樹可以通過 layer的 layer.presentationLayer 獲得; 渲染樹是私有的、無法訪問到; 而模型樹則可以通過 layer.modelLayer 屬性獲得,模型樹的屬性在其被修改的時候就變成了新的值,這個是可以用代碼直接操控的部分。

呈現(xiàn)樹的屬性值和動畫運行過程中界面上看到的是一致的; 渲染樹是對呈現(xiàn)樹的數(shù)據(jù)進行渲染,為了不阻塞主線程,渲染的過程是在單獨的進程或線程中進行的,所以你會發(fā)現(xiàn) Animation 的動畫并不會阻塞主線程。

CALayer 的那些可用于動畫的(Animatable)屬性,稱之為 Animatable Properties。如果一個 Layer 對象存在對應(yīng)著的 View,則稱這個 Layer 是一個Root Layer;非 Root Layer 一般都是通過 CALayer 或其子類直接創(chuàng)建的、所有的非 Root Layer 在設(shè)置 Animatable Properties 的時候都存在著隱式動畫,默認的 duration 是 0.25 秒。

任何 Layer 的 animatable Property 的設(shè)置都應(yīng)該屬于某個 CA 事務(wù)(CATransaction),事務(wù)的作用是為了保證多個 animatable 屬性的變化同時進行,不管是同一個 layer 還是不同的 layer 之間的。CATransaction 也分兩類:顯式的和隱式的。當(dāng)在某次 RunLoop 中設(shè)置一個 animatable 屬性的時候,如果發(fā)現(xiàn)當(dāng)前沒有事務(wù),則會自動創(chuàng)建一個 CA 事務(wù),在線程的下個 RunLoop 開始時自動 commit 這個事務(wù)。如果在沒有 RunLoop 的地方設(shè)置 layer 的animatable Property,則必須使用顯式的事務(wù) (PS:子線程中設(shè)置 layer.backgroundColor 后必須顯示的調(diào)用"[CATransaction commit];"才行)。

CATransaction 是 Core Animation 中的事務(wù)類,在 iOS 的圖層中、圖層的每個改變都是事務(wù)的一部分,CATransaction 可以對多個 layer 的屬性同時進行修改,同時負責(zé)批量的把多個圖層樹的修改作為一個原子更新到渲染樹。

CATransaction 事務(wù)類分為隱式事務(wù)和顯式事務(wù)。

注意以下兩組概念的區(qū)分:

隱式動畫和隱式事務(wù) - 隱式動畫通過隱式事務(wù)實現(xiàn)動畫。

顯式動畫和顯式事務(wù) - 顯式動畫有多種實現(xiàn)方式,顯式事務(wù)是一種實現(xiàn)顯式動畫的方式。

隱式事務(wù)是基于 CALayer的,任何對于 CALayer 屬性的修改都是隱式事務(wù)、這樣的事務(wù)會在 runloop 中被提交。

隱式事務(wù)是 CoreAnimation 的一部分,是對 layer-tree 進行原子更新為 render-tree 的機制,由 CoreAnimation 幫助創(chuàng)建事務(wù)。當(dāng)前線程的 runloop下次循環(huán)就會自動 commit,如果當(dāng)前線程沒有 runloop,或者 runloop 被阻塞,則應(yīng)該使用顯示事務(wù)。所謂的隱式動畫是因為我們并沒有指定任何動畫的類型,我們僅僅改變了一個屬性,然后 Core Animation 來決定如何并且何時去做動畫。但當(dāng)你改變一個屬性,Core Animation 是如何判斷動畫類型和持續(xù)時間的呢? 實際上動畫執(zhí)行的時間取決于當(dāng)前事務(wù)的設(shè)置,動畫類型取決于圖層行為。事務(wù)實際上是 Core Animation 用來包含一系列屬性動畫集合的機制,任何用指定事務(wù)去改變可以做動畫的圖層屬性都不會立刻發(fā)生變化,而是當(dāng)事務(wù)一旦提交的時候開始用一個動畫過渡到新值。

調(diào)用 CATransaction 的 begin 和 commit 方法就可以實現(xiàn)顯示事務(wù)。


線程是如何捕捉到 CATransaction 的 ?

調(diào)用 CATransaction 的 begin 和 commit 方法實現(xiàn)顯示事務(wù)、包含在begin & commit之間的代碼就可以被捕捉。

那么其他的情況呢? 比如直接修改layer的contents屬性的情況。

(1)在臨時子線程中修改layer的屬性:

如上所示當(dāng)修改了layer的背景色之后layer并不會立即就進行更新。而是等到子線程退出時才會進行更新、此時會有不可預(yù)估的時間延遲(當(dāng)修改layer的背景色之后會延遲幾秒亦或幾分鐘后背景色才會被更新)。原因是:當(dāng)子線程退出時會調(diào)用_pthread_tsd_cleanup繼而調(diào)用CATransaction的Transaction::release_thread方法、接著調(diào)用Transaction::commit()方法、在commit方法中對layer的修改才會被提交到GPU進行繪制。

以上的整個過程不會喚醒main runloop、更不會引起main runloop的運行狀態(tài)變化。這也從側(cè)面證實了渲染的過程是在單獨的進程或線程中進行的、并不會阻塞主線程。

getSubThreadInQueue方法是從GCD線程池里請求大量的線程、目的是讓系統(tǒng)盡快釋放不再需要的線程、便于調(diào)試:


(2)臨時子線程中修改layer的屬性、顯示地調(diào)用commit:

顯示的調(diào)用Transaction::commit()方法、在commit方法中對layer的修改會被提交到GPU進行繪制、此時layer會立即更新、不會出現(xiàn)延遲的情況。

如上所示的代碼中對CATransaction設(shè)置的CompletionBlock并不會在commit之后被調(diào)用!(由于臨時子線程中沒有runloop的原因、調(diào)用時機是系統(tǒng)對當(dāng)前線程進行回收之時、(添加了斷點:_pthread_tsd_cleanup))

以上的整個過程不會喚醒mainrunloop、更不會引起main runloop的運行狀態(tài)變化。這也從側(cè)面證實了渲染的過程是在單獨的進程或線程中進行的、并不會阻塞主線程。


(3)常駐子線程中修改layer的屬性:


修改layer的property對當(dāng)前線程的runloop產(chǎn)生的影響:

在修改layer的contents屬性之前、查看當(dāng)前線程中runloop的狀態(tài) (po [NSRunLoop currentRunLoop]):

當(dāng)修改了layer的contents屬性之后、當(dāng)前線程的runloop的變化(添加了一個監(jiān)聽Transaction的observer):


如上圖所示:app啟動后main runloop會在圖層樹第一次被修改的時候添加一個Transaction的Observer監(jiān)聽、監(jiān)聽BeforeWaiting & Exit、其回調(diào)函數(shù)是"_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv"也就是主線程里會捕捉到CATransaction的原因。

PS: 在“application:didFinishLaunchingWithOptions:”處打斷點查看po currentRunloop、可發(fā)現(xiàn)此時尚未創(chuàng)建這個Transaction的監(jiān)聽。


注意事項

當(dāng)前線程的 runloop 狀態(tài)變?yōu)?BeforeWaiting 或者 Exit 時、在回調(diào)的'CA::Transaction::observer_callback'方法中并不是每次都會調(diào)用'CA::Transaction::commit'方法。

如果存放 layer 屬性更改的容器中沒有元素、那么在執(zhí)行回調(diào)函數(shù)'CA::Transaction::observer_callback'的方法中就不會再調(diào)用'CA::Transaction::commit'方法了。

可添加以下斷點、自行進行調(diào)試:


CATransaction如何使用 ?

使用案例(QATextTransaction) GitHub - Avery-AN/TableView: TableView優(yōu)化案例、Cell展示內(nèi)容的異步繪制、Cell展示圖片的異步解碼與字節(jié)對齊的處理、TableView首屏cell渲染的提速、支持Cell展示內(nèi)容中包含的各種富文本的交互。


【 請勿直接轉(zhuǎn)載 - 節(jié)約能源從你我做起 】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容