使用 ASDK 性能調(diào)優(yōu) - 提升 iOS 界面的渲染性能

這一系列的文章會(huì)從幾個(gè)方面對(duì) ASDK 在性能調(diào)優(yōu)方面策略的實(shí)現(xiàn)進(jìn)行分析,幫助讀者理解 ASDK 如何做到使復(fù)雜的 UI 界面達(dá)到 60 FPS 的刷新頻率的候学;本篇文章會(huì)從視圖的渲染層面講解 ASDK 對(duì)于渲染過程的優(yōu)化并對(duì) ASDK 進(jìn)行概述。

在客戶端或者前端開發(fā)中悬钳,對(duì)于性能的優(yōu)化蕴掏,尤其是 UI冀瓦,往往都不是最先考慮的問題。

因?yàn)樵诖蠖鄶?shù)場(chǎng)景下丰辣,使用更加復(fù)雜的高性能代碼替代可用的代碼經(jīng)常會(huì)導(dǎo)致代碼的可維護(hù)性下降撒强,所以更需要我們開發(fā)者對(duì)優(yōu)化的時(shí)間點(diǎn)以及原因有一個(gè)比較清楚的認(rèn)識(shí),避免過度優(yōu)化帶來的問題笙什。

對(duì) iOS 開發(fā)比較熟悉的開發(fā)者都知道飘哨,iOS 中的性能問題大多是阻塞主線程導(dǎo)致用戶的交互反饋出現(xiàn)可以感知的延遲。

scrollview-demo

詳細(xì)說起來琐凭,大體有三種原因:

  1. UI 渲染需要時(shí)間較長芽隆,無法按時(shí)提交結(jié)果;
  2. 一些需要密集計(jì)算的處理放在了主線程中執(zhí)行统屈,導(dǎo)致主線程被阻塞胚吁,無法渲染 UI 界面;
  3. 網(wǎng)絡(luò)請(qǐng)求由于網(wǎng)絡(luò)狀態(tài)的問題響應(yīng)較慢愁憔,UI 層由于沒有模型返回?zé)o法渲染腕扶。

上面的這些問題都會(huì)影響應(yīng)用的性能,最常見的表現(xiàn)就是 UITableView 在滑動(dòng)時(shí)沒有達(dá)到 60 FPS吨掌,用戶能感受到明顯的卡頓蕉毯。

屏幕的渲染

相信點(diǎn)開這篇文章的大多數(shù)開發(fā)者都知道 FPS 是什么,那么如果才能優(yōu)化我們的 App 使其達(dá)到 60 FPS 呢思犁?在具體了解方法之前,我們先退一步进肯,提出另一個(gè)問題激蹲,屏幕是如何渲染的?

對(duì)于第一個(gè)問題江掩,可能需要幾篇文章來回答学辱,希望整個(gè)系列的文章能給你一個(gè)滿意的答案。3

CRT 和 LCD

屏幕的渲染可能要從 CRT(Cathode ray tube) 顯示器LCD(Liquid-crystal display) 顯示器講起环形。

CRT

CRT 顯示器是比較古老的技術(shù)策泣,它使用陰極電子槍發(fā)射電子,在陰極高壓的作用下抬吟,電子由電子槍射向熒光屏萨咕,使熒光粉發(fā)光,將圖像顯示在屏幕上火本,這也就是用磁鐵靠近一些老式電視機(jī)的屏幕會(huì)讓它們變色的原因危队。

而 FPS 就是 CRT 顯示器的刷新頻率聪建,電子槍每秒會(huì)對(duì)顯示器上內(nèi)容進(jìn)行 60 - 100 次的刷新,哪怕在我們看來沒有任何改變茫陆。

lcd

但是 LCD 的原理與 CRT 非常不同金麸,LCD 的成像原理跟光學(xué)有關(guān):

  • 在不加電壓下,光線會(huì)沿著液晶分子的間隙前進(jìn)旋轉(zhuǎn) 90°簿盅,所以光可以通過挥下;
  • 在加入電壓之后,光沿著液晶分子的間隙直線前進(jìn)桨醋,被濾光板擋住棚瘟。

如果你可以翻墻映之,相信下面的視頻會(huì)更好得幫助你理解 LCD 的工作原理:

<a >

</a>

LCD 的成像原理雖然與 CRT 截然不同浪南,每一個(gè)像素的顏色可以在需要改變時(shí)才去改變電壓,也就是不需要刷新頻率挺尾,但是由于一些歷史原因返顺,LCD 仍然需要按照一定的刷新頻率向 GPU 獲取新的圖像用于顯示禀苦。

屏幕撕裂

但是顯示器只是用于將圖像顯示在屏幕上,誰又是圖像的提供者呢遂鹊?圖像都是我們經(jīng)常說的 GPU 提供的振乏。

而這導(dǎo)致了另一個(gè)問題,由于 GPU 生成圖像的頻率與顯示器刷新的頻率是不相關(guān)的秉扑,那么在顯示器刷新時(shí)慧邮,GPU 沒有準(zhǔn)備好需要顯示的圖像怎么辦;或者 GPU 的渲染速度過快舟陆,顯示器來不及刷新误澳,GPU 就已經(jīng)開始渲染下一幀圖像又該如何處理?

screen-tearing

如果解決不了這兩個(gè)問題秦躯,就會(huì)出現(xiàn)上圖中的屏幕撕裂(Screen Tearing)現(xiàn)象忆谓,屏幕中一部分顯示的是上一幀的內(nèi)容,另一部分顯示的是下一幀的內(nèi)容踱承。

我們用兩個(gè)例子來說明可能出現(xiàn)屏幕撕裂的兩種情況:

  • 如果顯示器的刷新頻率為 75 Hz倡缠,GPU 的渲染速度為 100 Hz,那么在兩次屏幕刷新的間隔中茎活,GPU 會(huì)渲染 4/3 個(gè)幀昙沦,后面的 1/3 幀會(huì)覆蓋已經(jīng)渲染好的幀棧,最終會(huì)導(dǎo)致屏幕在 1/3 或者 2/3 的位置出現(xiàn)屏幕撕裂效果载荔;
  • 那么 GPU 的渲染速度小于顯示器呢盾饮,比如說 50 Hz,那么在兩次屏幕刷新的間隔中,GPU 只會(huì)渲染 2/3 幀丐谋,剩下的 1/3 會(huì)來自上一幀芍碧,與上面的結(jié)果完全相同,在同樣的位置出現(xiàn)撕裂效果号俐。

到這里泌豆,有人會(huì)說,如果顯示器的刷新頻率與 GPU 的渲染速度完全相同吏饿,應(yīng)該就會(huì)解決屏幕撕裂的問題了吧踪危?其實(shí)并不是。顯示器從 GPU 拷貝幀的過程依然需要消耗一定的時(shí)間猪落,如果屏幕在拷貝圖像時(shí)刷新贞远,仍然會(huì)導(dǎo)致屏幕撕裂問題。

how-to-solve-tearing-proble

引入多個(gè)緩沖區(qū)可以有效地緩解屏幕撕裂笨忌,也就是同時(shí)使用一個(gè)幀緩沖區(qū)(frame buffer)和多個(gè)后備緩沖區(qū)(back buffer)蓝仲;在每次顯示器請(qǐng)求內(nèi)容時(shí),都會(huì)從幀緩沖區(qū)中取出圖像然后渲染官疲。

雖然緩沖區(qū)可以減緩這些問題袱结,但是卻不能解決;如果后備緩沖區(qū)繪制完成途凫,而幀緩沖區(qū)的圖像沒有被渲染垢夹,后備緩沖區(qū)中的圖像就會(huì)覆蓋幀緩沖區(qū),仍然會(huì)導(dǎo)致屏幕撕裂维费。

解決這個(gè)問題需要另一個(gè)機(jī)制的幫助果元,也就是垂直同步(Vertical synchronization),簡(jiǎn)稱 V-Sync 來解決犀盟。

V-Sync

V-Sync 的主要作用就是保證只有在幀緩沖區(qū)中的圖像被渲染之后而晒,后備緩沖區(qū)中的內(nèi)容才可以被拷貝到幀緩沖區(qū)中,理想情況下的 V-Sync 會(huì)按這種方式工作:

normal-vsyn

每次 V-Sync 發(fā)生時(shí)阅畴,CPU 以及 GPU 都已經(jīng)完成了對(duì)圖像的處理以及繪制倡怎,顯示器可以直接拿到緩沖區(qū)中的幀。但是恶阴,如果 CPU 或者 GPU 的處理需要的時(shí)間較長,就會(huì)發(fā)生掉幀的問題:

lag-vsyn

在 V-Sync 信號(hào)發(fā)出時(shí)豹障,CPU 和 GPU 并沒有準(zhǔn)備好需要渲染的幀冯事,顯示器就會(huì)繼續(xù)使用當(dāng)前幀,這就加劇了屏幕的顯示問題血公,而每秒顯示的幀數(shù)會(huì)少于 60昵仅。

由于會(huì)發(fā)生很多次掉幀,在開啟了 V-Sync 后,40 ~ 50 FPS 的渲染頻率意味著顯示器輸出的畫面幀率會(huì)從 60 FPS 急劇下降到 30 FPS摔笤,原因在這里不會(huì)解釋够滑,讀者可以自行思考。

其實(shí)到這里關(guān)于屏幕渲染的內(nèi)容就已經(jīng)差不多結(jié)束了吕世,根據(jù) V-Sync 的原理彰触,優(yōu)化應(yīng)用性能、提高 App 的 FPS 就可以從兩個(gè)方面來入手命辖,優(yōu)化 CPU 以及 GPU 的處理時(shí)間况毅。

讀者也可以從 iOS 保持界面流暢的技巧這篇文章中了解更多的相關(guān)內(nèi)容。

性能調(diào)優(yōu)的策略

CPU 和 GPU 在每次 V-Sync 時(shí)間點(diǎn)到達(dá)之前都在干什么尔艇?如果尔许,我們知道了它們各自負(fù)責(zé)的工作,通過優(yōu)化代碼就可以提升性能终娃。

cpu-gpu

很多 CPU 的操作都會(huì)延遲 GPU 開始渲染的時(shí)間:

  • 布局的計(jì)算 - 如果你的視圖層級(jí)太過于復(fù)雜味廊,或者視圖需要重復(fù)多次進(jìn)行布局,尤其是在使用 Auto Layout 進(jìn)行自動(dòng)布局時(shí)棠耕,對(duì)性能影響尤為嚴(yán)重余佛;
  • 視圖的惰性加載 - 在 iOS 中只有當(dāng)視圖控制器的視圖顯示到屏幕時(shí)才會(huì)加載;
  • 解壓圖片 - iOS 通常會(huì)在真正繪制時(shí)才會(huì)解碼圖片昧辽,對(duì)于一個(gè)較大的圖片衙熔,無論是直接或間接使用 UIImageView 或者繪制到 Core Graphics 中,都需要對(duì)圖片進(jìn)行解壓搅荞;
  • ...

寬泛的說红氯,大多數(shù)的 CALayer 的屬性都是由 GPU 來繪制的,比如圖片的圓角咕痛、變換痢甘、應(yīng)用紋理;但是過多的幾何結(jié)構(gòu)茉贡、重繪塞栅、離屏繪制(Offscrren)以及過大的圖片都會(huì)導(dǎo)致 GPU 的性能明顯降低。

上面的內(nèi)容出自 CPU vs GPU · iOS 核心動(dòng)畫高級(jí)技巧腔丧,你可以在上述文章中對(duì) CPU 和 GPU 到底各自做了什么有一個(gè)更深的了解放椰。

也就是說,如果我們解決了上述問題愉粤,就能加快應(yīng)用的渲染速度砾医,大大提升用戶體驗(yàn)。

AsyncDisplayKit

文章的前半部分已經(jīng)從屏幕的渲染原理講到了性能調(diào)優(yōu)的幾個(gè)策略衣厘;而 AsyncDisplayKit 就根據(jù)上述的策略幫助我們對(duì)應(yīng)用性能進(jìn)行優(yōu)化如蚜。

asdk-logo

AsyncDisplayKit(以下簡(jiǎn)稱 ASDK)是由 Facebook 開源的一個(gè) iOS 框架压恒,能夠幫助最復(fù)雜的 UI 界面保持流暢和快速響應(yīng)。

ASDK 從開發(fā)到開源大約經(jīng)歷了一年多的時(shí)間错邦,它其實(shí)并不是一個(gè)簡(jiǎn)單的框架它是一個(gè)復(fù)雜的框架探赫,更像是對(duì) UIKit 的重新實(shí)現(xiàn),把整個(gè) UIKit 以及 CALayer 層封裝成一個(gè)一個(gè) Node撬呢,將昂貴的渲染伦吠、圖片解碼、布局以及其它 UI 操作移出主線程倾芝,這樣主線程就可以對(duì)用戶的操作及時(shí)做出反應(yīng)讨勤。

很多分析 ASDK 的文章都會(huì)有這么一張圖介紹框架中的最基本概念:

asdk-hierarchy

在 ASDK 中最基本的單位就是 ASDisplayNode,每一個(gè) node 都是對(duì) UIView 以及 CALayer 的抽象晨另。但是與 UIView 不同的是潭千,ASDisplayNode 是線程安全的,它可以在后臺(tái)線程中完成初始化以及配置工作借尿。

如果按照 60 FPS 的刷新頻率來計(jì)算刨晴,每一幀的渲染時(shí)間只有 16ms,在 16ms 的時(shí)間內(nèi)要完成對(duì) UIView 的創(chuàng)建路翻、布局狈癞、繪制以及渲染,CPU 和 GPU 面臨著巨大的壓力茂契。

apple-a9

但是從 A5 處理器之后蝶桶,多核的設(shè)備成為了主流,原有的將所有操作放入主線程的實(shí)踐已經(jīng)不能適應(yīng)復(fù)雜的 UI 界面掉冶,所以 ASDK 將耗時(shí)的 CPU 操作以及 GPU 渲染紋理(Texture)的過程全部放入后臺(tái)進(jìn)程真竖,使主線程能夠快速響應(yīng)用戶操作

ASDK 通過獨(dú)特的渲染技巧厌小、代替 AutoLayout 的布局系統(tǒng)恢共、智能的預(yù)加載方式等模塊來實(shí)現(xiàn)對(duì) App 性能的優(yōu)化。

ASDK 的渲染過程

ASDK 中到底使用了哪些方法來對(duì)視圖進(jìn)行渲染呢璧亚?本文主要會(huì)從渲染的過程開始分析讨韭,了解 ASDK 底層如何提升界面的渲染性能。

在 ASDK 中的渲染圍繞 ASDisplayNode 進(jìn)行癣蟋,其過程總共有四條主線:

  • 初始化 ASDisplayNode 對(duì)應(yīng)的 UIView 或者 CALayer透硝;
  • 在當(dāng)前視圖進(jìn)入視圖層級(jí)時(shí)執(zhí)行 setNeedsDisplay
  • display 方法執(zhí)行時(shí)疯搅,向后臺(tái)線程派發(fā)繪制事務(wù)濒生;
  • 注冊(cè)成為 RunLoop 觀察者,在每個(gè) RunLoop 結(jié)束時(shí)回調(diào)秉撇。

UIView 和 CALayer 的加載

當(dāng)我們運(yùn)行某一個(gè)使用 ASDK 的工程時(shí)甜攀,-[ASDisplayNode _loadViewOrLayerIsLayerBacked:] 總是 ASDK 中最先被調(diào)用的方法,而這個(gè)方法執(zhí)行的原因往往就是 ASDisplayNode 對(duì)應(yīng)的 UIViewCALayer 被引用了:

- (CALayer *)layer {
    if (!_layer) {
        ASDisplayNodeAssertMainThread();

        if (!_flags.layerBacked) return self.view.layer;
        [self _loadViewOrLayerIsLayerBacked:YES];
    }
    return _layer;
}

- (UIView *)view {
    if (_flags.layerBacked) return nil;
    if (!_view) {
        ASDisplayNodeAssertMainThread();
        [self _loadViewOrLayerIsLayerBacked:NO];
    }
    return _view;
}

這里涉及到一個(gè) ASDK 中比較重要的概念琐馆,如果 ASDisplayNodelayerBacked 的规阀,它不會(huì)渲染對(duì)應(yīng)的 UIView 以此來提升性能:

- (void)_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked {
    if (isLayerBacked) {
        _layer = [self _layerToLoad];
        _layer.delegate = (id<CALayerDelegate>)self;
    } else {
        _view = [self _viewToLoad];
        _view.asyncdisplaykit_node = self;
        _layer = _view.layer;
    }
    _layer.asyncdisplaykit_node = self;

    self.asyncLayer.asyncDelegate = self;
}

因?yàn)?UIViewCALayer 雖然都可以用于展示內(nèi)容,不過由于 UIView 可以用于處理用戶的交互瘦麸,所以如果不需要使用 UIView 的特性谁撼,直接使用 CALayer 進(jìn)行渲染,能夠節(jié)省大量的渲染時(shí)間滋饲。

如果你使用 Xcode 查看過視圖的層級(jí)厉碟,那么你應(yīng)該知道,UIView 在 Debug View Hierarchy 中是有層級(jí)的屠缭;而 CALayer 并沒有箍鼓,它門的顯示都在一個(gè)平面上。

上述方法中的 -[ASDisplayNode _layerToLoad] 以及 [ASDisplayNode _viewToLoad] 都只會(huì)根據(jù)當(dāng)前節(jié)點(diǎn)的 layerClass 或者 viewClass 初始化一個(gè)對(duì)象呵曹。

Layer Trees vs. Flat Drawing – Graphics Performance Across iOS Device Generations 這篇文章比較了 UIViewCALayer 的渲染時(shí)間款咖。

view-layer-cg-compare

-[ASDisplayNode asyncLayer] 只是對(duì)當(dāng)前 node 持有的 layer 進(jìn)行封裝,確保會(huì)返回一個(gè) _ASDisplayLayer 的實(shí)例:

- (_ASDisplayLayer *)asyncLayer {
    ASDN::MutexLocker l(_propertyLock);
    return [_layer isKindOfClass:[_ASDisplayLayer class]] ? (_ASDisplayLayer *)_layer : nil;
}

最重要的是 -[ASDisplayNode _loadViewOrLayerIsLayerBacked:] 方法會(huì)將當(dāng)前節(jié)點(diǎn)設(shè)置為 asyncLayer 的代理奄喂,在后面會(huì)使用 ASDisplayNodeCALayer 渲染內(nèi)容铐殃。

視圖層級(jí)

在初始化工作完成之后,當(dāng) ASDisplayNode 第一次被加入到視圖的層級(jí)時(shí)跨新,-[_ASDisplayView willMoveToWindow:] 就會(huì)被調(diào)用富腊。

_ASDisplayView 和 _ASDisplayLayer

_ASDisplayView_ASDisplayLayer 都是私有類,它們之間的對(duì)應(yīng)關(guān)系其實(shí)和 UIViewCALayer 完全相同域帐。

+ (Class)layerClass {
    return [_ASDisplayLayer class];
}

_ASDisplayView 覆寫了很多跟視圖層級(jí)改變有關(guān)的方法:

  • -[_ASDisplayView willMoveToWindow:]
  • -[_ASDisplayView didMoveToWindow]
  • -[_ASDisplayView willMoveToSuperview:]
  • -[_ASDisplayView didMoveToSuperview]

它們用于在視圖的層級(jí)改變時(shí)赘被,通知對(duì)應(yīng) ASDisplayNode 作出相應(yīng)的反應(yīng),比如 -[_ASDisplayView willMoveToWindow:] 方法會(huì)在視圖被加入層級(jí)時(shí)調(diào)用:

- (void)willMoveToWindow:(UIWindow *)newWindow {
    BOOL visible = (newWindow != nil);
    if (visible && !_node.inHierarchy) {
        [_node __enterHierarchy];
    }
}

setNeedsDisplay

當(dāng)前視圖如果不在視圖層級(jí)中俯树,就會(huì)通過 _node 的實(shí)例方法 -[ASDisplayNode __enterHierarchy] 加入視圖層級(jí):

- (void)__enterHierarchy {
    if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) {
        _flags.isEnteringHierarchy = YES;
        _flags.isInHierarchy = YES;

        if (_flags.shouldRasterizeDescendants) {
            [self _recursiveWillEnterHierarchy];
        } else {
            [self willEnterHierarchy];
        }
        _flags.isEnteringHierarchy = NO;

        # 更新 layer 顯示的內(nèi)容
    }
}
        

_flagsASDisplayNodeFlags 結(jié)構(gòu)體帘腹,用于標(biāo)記當(dāng)前 ASDisplayNode 的一些 BOOL 值,比如许饿,異步顯示阳欲、柵格化子視圖等等,你不需要知道都有什么陋率,根據(jù)這些值的字面意思理解就已經(jīng)足夠了球化。

上述方法的前半部分只是對(duì) _flags 的標(biāo)記,如果需要將當(dāng)前視圖的子視圖柵格化瓦糟,也就是將它的全部子視圖與當(dāng)前視圖壓縮成一個(gè)圖層筒愚,就會(huì)向這些視圖遞歸地調(diào)用 -[ASDisplayNode willEnterHierarchy] 方法通知目前的狀態(tài):

- (void)_recursiveWillEnterHierarchy {
  _flags.isEnteringHierarchy = YES;
  [self willEnterHierarchy];
  _flags.isEnteringHierarchy = NO;

  for (ASDisplayNode *subnode in self.subnodes) {
    [subnode _recursiveWillEnterHierarchy];
  }
}

-[ASDisplayNode willEnterHierarchy] 會(huì)修改當(dāng)前節(jié)點(diǎn)的 interfaceStateASInterfaceStateInHierarchy,表示當(dāng)前節(jié)點(diǎn)不包含在 cell 或者其它菩浙,但是在 window 中巢掺。

- (void)willEnterHierarchy {
  if (![self supportsRangeManagedInterfaceState]) {
    self.interfaceState = ASInterfaceStateInHierarchy;
  }
}

當(dāng)前結(jié)點(diǎn)需要被顯示在屏幕上時(shí)句伶,如果其內(nèi)容 contents 為空,就會(huì)調(diào)用 -[CALayer setNeedsDisplay] 方法將 CALayer 標(biāo)記為臟的陆淀,通知系統(tǒng)需要在下一個(gè)繪制循環(huán)中重繪視圖:

- (void)__enterHierarchy {
     if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) {

        # 標(biāo)記節(jié)點(diǎn)的 flag
        
        if (self.contents == nil) {
            CALayer *layer = self.layer;
            [layer setNeedsDisplay];

            if ([self _shouldHavePlaceholderLayer]) {
                [CATransaction begin];
                [CATransaction setDisableActions:YES];
                [self _setupPlaceholderLayerIfNeeded];
                _placeholderLayer.opacity = 1.0;
                [CATransaction commit];
                [layer addSublayer:_placeholderLayer];
            }
        }
    }
}

在將 CALayer 標(biāo)記為 dirty 之后考余,在繪制循環(huán)中就會(huì)執(zhí)行 -[CALayer display] 方法,對(duì)它要展示的內(nèi)容進(jìn)行繪制轧苫;如果當(dāng)前視圖需要一些占位圖楚堤,那么就會(huì)在這里的代碼中,為當(dāng)前 node 對(duì)應(yīng)的 layer 添加合適顏色的占位層含懊。

placeholder-laye

派發(fā)異步繪制事務(wù)

在上一節(jié)中調(diào)用 -[CALayer setNeedsDisplay] 方法將當(dāng)前節(jié)點(diǎn)標(biāo)記為 dirty 之后身冬,在下一個(gè)繪制循環(huán)時(shí)就會(huì)對(duì)所有需要重繪的 CALayer 執(zhí)行 -[CALayer display],這也是這一小節(jié)需要分析的方法的入口:

- (void)display {
  [self _hackResetNeedsDisplay];

  ASDisplayNodeAssertMainThread();
  if (self.isDisplaySuspended) return;

  [self display:self.displaysAsynchronously];
}

這一方法的調(diào)用棧比較復(fù)雜岔乔,在具體分析之前酥筝,筆者會(huì)先給出這個(gè)方法的調(diào)用棧,給讀者一個(gè)關(guān)于該方法實(shí)現(xiàn)的簡(jiǎn)要印象:

-[_ASDisplayLayer display]
    -[_ASDisplayLayer display:] // 將繪制工作交給 ASDisplayNode 處理
        -[ASDisplayNode(AsyncDisplay) displayAsyncLayer:asynchronously:]
            -[ASDisplayNode(AsyncDisplay) _displayBlockWithAsynchronous:isCancelledBlock:rasterizing:]
                -[ASDisplayNode(AsyncDisplay) _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:displayBlocks:]            
            -[CALayer(ASDisplayNodeAsyncTransactionContainer) asyncdisplaykit_parentTransactionContainer]
            -[CALayer(ASDisplayNodeAsyncTransactionContainer) asyncdisplaykit_asyncTransaction]
                -[_ASAsyncTransaction initWithCallbackQueue:completionBlock:]
                -[_ASAsyncTransactionGroup addTransactionContainer:]
            -[_ASAsyncTransaction addOperationWithBlock:priority:queue:completion:]
                ASAsyncTransactionQueue::GroupImpl::schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block)
                    void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

-[_ASDisplayLayer display] 在調(diào)用棧中其實(shí)會(huì)創(chuàng)建一個(gè) displayBlock雏门,它其實(shí)是一個(gè)使用 Core Graphics 進(jìn)行圖像繪制的過程樱哼,整個(gè)繪制過程是通過事務(wù)的形式進(jìn)行管理的;而 displayBlock 會(huì)被 GCD 分發(fā)到后臺(tái)的并發(fā)進(jìn)程來處理剿配。

調(diào)用棧中的第二個(gè)方法 -[_ASDisplayLayer display] 會(huì)將異步繪制的工作交給自己的 asyncDelegate搅幅,也就是第一部分中設(shè)置的 ASDisplayNode

- (void)display:(BOOL)asynchronously {
  [_asyncDelegate displayAsyncLayer:self asynchronously:asynchronously];
}

ASDisplayNode(AsyncDisplay)

這里省略了一部分 -[ASDisplayNode(AsyncDisplay) displayAsyncLayer:asynchronously:] 方法的實(shí)現(xiàn):

- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously {
  ASDisplayNodeAssertMainThread();

  ...

  asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:asynchronously isCancelledBlock:isCancelledBlock rasterizing:NO];
  
  if (!displayBlock) return;
  
  asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled){
    ASDisplayNodeCAssertMainThread();
    if (!canceled && !isCancelledBlock()) {
      UIImage *image = (UIImage *)value;
      _layer.contentsScale = self.contentsScale;
      _layer.contents = (id)image.CGImage;
    }
  };

  if (asynchronously) {
    CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ? : _layer;
    _ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction;
    [transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock];
  } else {
    UIImage *contents = (UIImage *)displayBlock();
    completionBlock(contents, NO);
  }
}

省略后的代碼脈絡(luò)非常清晰,-[ASDisplayNode(AsyncDisplay) _displayBlockWithAsynchronous:isCancelledBlock:rasterizing:] 返回一個(gè)用于 displayBlock呼胚,然后構(gòu)造一個(gè) completionBlock茄唐,在繪制結(jié)束時(shí)執(zhí)行,在主線程中設(shè)置當(dāng)前 layer 的內(nèi)容蝇更。

如果當(dāng)前的渲染是異步的沪编,就會(huì)將 displayBlock 包裝成一個(gè)事務(wù),添加到隊(duì)列中執(zhí)行年扩,否則就會(huì)同步執(zhí)行當(dāng)前的 block蚁廓,并執(zhí)行 completionBlock 回調(diào),通知 layer 更新顯示的內(nèi)容厨幻。

同步顯示的部分到這里已經(jīng)很清楚了相嵌,我們更關(guān)心的其實(shí)還是異步繪制的部分,因?yàn)檫@部分才是 ASDK 提升效率的關(guān)鍵况脆;而這就要從獲取 displayBlock 的方法開始了解了饭宾。

displayBlock 的構(gòu)建

displayBlock 的創(chuàng)建一般分為三種不同的方式:

  1. 將當(dāng)前視圖的子視圖壓縮成一層繪制在當(dāng)前頁面上
  2. 使用 - displayWithParameters:isCancelled: 返回一個(gè) UIImage,對(duì)圖像節(jié)點(diǎn) ASImageNode 進(jìn)行繪制
  3. 使用 - drawRect:withParameters:isCancelled:isRasterizing: 在 CG 上下文中繪制文字節(jié)點(diǎn) ASTextNode

這三種方式都通過 ASDK 來優(yōu)化視圖的渲染速度格了,這些操作最后都會(huì)扔到后臺(tái)的并發(fā)線程中進(jìn)行處理看铆。

下面三個(gè)部分的代碼經(jīng)過了刪減,省略了包括取消繪制盛末、通知代理弹惦、控制并發(fā)數(shù)量以及用于調(diào)試的代碼否淤。

柵格化子視圖

如果當(dāng)前的視圖需要柵格化子視圖,就會(huì)進(jìn)入啟用下面的構(gòu)造方式創(chuàng)建一個(gè) block棠隐,它會(huì)遞歸地將子視圖繪制在父視圖上:

- (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock rasterizing:(BOOL)rasterizing {
  asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil;
  ASDisplayNodeFlags flags = _flags;

  if (!rasterizing && self.shouldRasterizeDescendants) {
    NSMutableArray *displayBlocks = [NSMutableArray array];
    [self _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks];

    CGFloat contentsScaleForDisplay = self.contentsScaleForDisplay;
    BOOL opaque = self.opaque && CGColorGetAlpha(self.backgroundColor.CGColor) == 1.0f;

    displayBlock = ^id{

      UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);

      for (dispatch_block_t block in displayBlocks) {
        block();
      }

      UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
      UIGraphicsEndImageContext();

      return image;
    };
  } else if (flags.implementsInstanceImageDisplay || flags.implementsImageDisplay) {
    #:繪制 UIImage
  } else if (flags.implementsInstanceDrawRect || flags.implementsDrawRect) {
    #:提供 context叹括,使用 CG 繪圖
  }

  return [displayBlock copy];
}

在壓縮視圖層級(jí)的過程中就會(huì)調(diào)用 -[ASDisplayNode(AsyncDisplay) _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:displayBlocks:] 方法,獲取子視圖的所有 displayBlock宵荒,在得到 UIGraphicsBeginImageContextWithOptions 需要的參數(shù)之后,創(chuàng)建一個(gè)新的 context净嘀,執(zhí)行了所有的 displayBlock 將子視圖的繪制到當(dāng)前圖層之后报咳,使用 UIGraphicsGetImageFromCurrentImageContext 取出圖層的內(nèi)容并返回。

-[ASDisplayNode(AsyncDisplay) _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:displayBlocks:] 的實(shí)現(xiàn)還是有些繁瑣的挖藏,它主要的功能就是使用 Core Graphics 進(jìn)行繪圖暑刃,將背景顏色、仿射變換膜眠、位置大小以及圓角等參數(shù)繪制到當(dāng)前的上下文中岩臣,而且這個(gè)過程是遞歸的,直到不存在或者不需要繪制子節(jié)點(diǎn)為止宵膨。

繪制圖片

displayBlock 的第二種繪制策略更多地適用于圖片節(jié)點(diǎn) ASImageNode 的繪制:

- (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock rasterizing:(BOOL)rasterizing {
  asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil;
  ASDisplayNodeFlags flags = _flags;

  if (!rasterizing && self.shouldRasterizeDescendants) {
    #:柵格化
  } else if (flags.implementsInstanceImageDisplay || flags.implementsImageDisplay) {
    id drawParameters = [self drawParameters];
    
    displayBlock = ^id{
      UIImage *result = nil;
      if (flags.implementsInstanceImageDisplay) {
        result = [self displayWithParameters:drawParameters isCancelled:isCancelledBlock];
      } else {
        result = [[self class] displayWithParameters:drawParameters isCancelled:isCancelledBlock];
      }
      return result;
    };
  } else if (flags.implementsInstanceDrawRect || flags.implementsDrawRect) {
    #:提供 context架谎,使用 CG 繪圖
  }

  return [displayBlock copy];
}

通過 - displayWithParameters:isCancelled: 的執(zhí)行返回一個(gè)圖片,不過這里的繪制也離不開 Core Graphics 的一些 C 函數(shù)辟躏,你會(huì)在 -[ASImageNode displayWithParameters:isCancelled:] 中看到對(duì)于 CG 的運(yùn)用谷扣,它會(huì)使用 drawParameters 來修改并繪制自己持有的 image 對(duì)象。

使用 CG 繪圖

文字的繪制一般都會(huì)在 - drawRect:withParameters:isCancelled:isRasterizing: 進(jìn)行捎琐,這個(gè)方法只是提供了一個(gè)合適的用于繪制的上下文会涎,該方法不止可以繪制文字,只是在這里繪制文字比較常見:

- (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock rasterizing:(BOOL)rasterizing {
  asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil;
  ASDisplayNodeFlags flags = _flags;

  if (!rasterizing && self.shouldRasterizeDescendants) {
    #:柵格化
  } else if (flags.implementsInstanceImageDisplay || flags.implementsImageDisplay) {
    #:繪制 UIImage
  } else if (flags.implementsInstanceDrawRect || flags.implementsDrawRect) {
      if (!rasterizing) {
        UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
      }

      if (flags.implementsInstanceDrawRect) {
        [self drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
      } else {
        [[self class] drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
      }
      
      UIImage *image = nil;
      if (!rasterizing) {
        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
      }

      return image;
    };
  }

  return [displayBlock copy];
}

上述代碼跟第一部分比較像瑞凑,區(qū)別是這里不會(huì)柵格化子視圖末秃;代碼根據(jù)情況會(huì)決定是否重新開一個(gè)新的上下文,然后通過 - drawRect:withParameters:isCancelled:isRasterizing: 方法實(shí)現(xiàn)繪制籽御。

管理繪制事務(wù)

ASDK 提供了一個(gè)私有的管理事務(wù)的機(jī)制练慕,由三部分組成 _ASAsyncTransactionGroup_ASAsyncTransactionContainer 以及 _ASAsyncTransaction技掏,這三者各自都有不同的功能:

  • _ASAsyncTransactionGroup 會(huì)在初始化時(shí)贺待,向 Runloop 中注冊(cè)一個(gè)回調(diào),在每次 Runloop 結(jié)束時(shí)零截,執(zhí)行回調(diào)來提交 displayBlock 執(zhí)行的結(jié)果
  • _ASAsyncTransactionContainer 為當(dāng)前 CALayer 提供了用于保存事務(wù)的容器麸塞,并提供了獲取新的 _ASAsyncTransaction 實(shí)例的便利方法
  • _ASAsyncTransaction 將異步操作封裝成了輕量級(jí)的事務(wù)對(duì)象,使用 C++ 代碼對(duì) GCD 進(jìn)行了封裝

從上面的小節(jié)中涧衙,我們已經(jīng)獲取到了用于繪制的 displayBlock哪工,然后就需要將 block 添加到繪制事務(wù)中:

- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously {
  ...

  if (asynchronously) {
    CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ? : _layer;
    _ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction;
    [transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock];
  } else {
    ...
  }
}

前兩行代碼是獲取 _ASAsyncTransaction 實(shí)例的過程奥此,這個(gè)實(shí)例會(huì)包含在一個(gè) layer 的哈希表中,最后調(diào)用的實(shí)例方法 -[_ASAsyncTransaction addOperationWithBlock:priority:queue:completion:] 會(huì)把用于繪制的 displayBlock 添加到后臺(tái)并行隊(duì)列中:

+ (dispatch_queue_t)displayQueue {
  static dispatch_queue_t displayQueue = NULL;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    displayQueue = dispatch_queue_create("org.AsyncDisplayKit.ASDisplayLayer.displayQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_set_target_queue(displayQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
  });

  return displayQueue;
}

這個(gè)隊(duì)列是一個(gè)并行隊(duì)列雁比,并且優(yōu)先級(jí)是 DISPATCH_QUEUE_PRIORITY_HIGH稚虎,確保 UI 的渲染會(huì)在其它異步操作執(zhí)行之前進(jìn)行,而 -[_ASAsyncTransaction addOperationWithBlock:priority:queue:completion:] 中會(huì)初始化 ASDisplayNodeAsyncTransactionOperation 的實(shí)例偎捎,然后傳入 completionBlock蠢终,在繪制結(jié)束時(shí)回調(diào):

- (void)addOperationWithBlock:(asyncdisplaykit_async_transaction_operation_block_t)block priority:(NSInteger)priority queue:(dispatch_queue_t)queue completion:(asyncdisplaykit_async_transaction_operation_completion_block_t)completion {
  ASDisplayNodeAssertMainThread();

  [self _ensureTransactionData];

  ASDisplayNodeAsyncTransactionOperation *operation = [[ASDisplayNodeAsyncTransactionOperation alloc] initWithOperationCompletionBlock:completion];
  [_operations addObject:operation];
  _group->schedule(priority, queue, ^{
    @autoreleasepool {
      operation.value = block();
    }
  });
}

schedule 方法是一個(gè) C++ 方法,它會(huì)向 ASAsyncTransactionQueue::Group 中派發(fā)一個(gè) block茴她,這個(gè) block 中會(huì)執(zhí)行 displayBlock寻拂,然后將結(jié)果傳給 operation.value

void ASAsyncTransactionQueue::GroupImpl::schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block) {
  ASAsyncTransactionQueue &q = _queue;
  ASDN::MutexLocker locker(q._mutex);
  
  DispatchEntry &entry = q._entries[queue];
  
  Operation operation;
  operation._block = block;
  operation._group = this;
  operation._priority = priority;
  entry.pushOperation(operation);
  
  ++_pendingOperations;
  
  NSUInteger maxThreads = [NSProcessInfo processInfo].activeProcessorCount * 2;

  if ([[NSRunLoop mainRunLoop].currentMode isEqualToString:UITrackingRunLoopMode])
    --maxThreads;
  
  if (entry._threadCount < maxThreads) {
    bool respectPriority = entry._threadCount > 0;
    ++entry._threadCount;
    
    dispatch_async(queue, ^{
      while (!entry._operationQueue.empty()) {
        Operation operation = entry.popNextOperation(respectPriority);
        {
          if (operation._block) {
            operation._block();
          }
          operation._group->leave();
          operation._block = nil;
        }
      }
      --entry._threadCount;
      
      if (entry._threadCount == 0) {
        q._entries.erase(queue);
      }
    });
  }
}

ASAsyncTransactionQueue::GroupImpl 其實(shí)現(xiàn)其實(shí)就是對(duì) GCD 的封裝,同時(shí)添加一些最大并發(fā)數(shù)丈牢、線程鎖的功能祭钉。通過 dispatch_async 將 block 分發(fā)到 queue 中,立刻執(zhí)行 block己沛,將數(shù)據(jù)傳回 ASDisplayNodeAsyncTransactionOperation 實(shí)例慌核。

回調(diào)

_ASAsyncTransactionGroup 調(diào)用 mainTransactionGroup 類方法獲取單例時(shí),會(huì)通過 +[_ASAsyncTransactionGroup registerTransactionGroupAsMainRunloopObserver] 向 Runloop 中注冊(cè)回調(diào):

+ (void)registerTransactionGroupAsMainRunloopObserver:(_ASAsyncTransactionGroup *)transactionGroup {
  static CFRunLoopObserverRef observer;
  CFRunLoopRef runLoop = CFRunLoopGetCurrent();
  CFOptionFlags activities = (kCFRunLoopBeforeWaiting | kCFRunLoopExit);
  CFRunLoopObserverContext context = {0, (__bridge void *)transactionGroup, &CFRetain, &CFRelease, NULL};

  observer = CFRunLoopObserverCreate(NULL, activities, YES, INT_MAX, &_transactionGroupRunLoopObserverCallback, &context);
  CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes);
  CFRelease(observer);
}

上述代碼會(huì)在即將退出 Runloop 或者 Runloop 開始休眠時(shí)執(zhí)行回調(diào) _transactionGroupRunLoopObserverCallback,而這個(gè)回調(diào)方法就是這一條主線的入口:

static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
  ASDisplayNodeCAssertMainThread();
  _ASAsyncTransactionGroup *group = (__bridge _ASAsyncTransactionGroup *)info;
  [group commit];
}

上一節(jié)中只是會(huì)將繪制代碼提交到后臺(tái)的并發(fā)進(jìn)程中,而這里才會(huì)將結(jié)果提交救湖,也就是在每次 Runloop 循環(huán)結(jié)束時(shí)開始繪制內(nèi)容振劳,而 -[_operationCompletionBlock commit] 方法的調(diào)用棧能夠幫助我們理解內(nèi)容是如何提交的,又是如何傳回 node 持有的 layer 的:

-[_ASAsyncTransactionGroup commit]
    -[_ASAsyncTransaction commit]
        ASAsyncTransactionQueue::GroupImpl::notify(dispatch_queue_t, dispatch_block_t)
            _notifyList.push_back(GroupNotify)

-[_ASAsyncTransactionGroup commit] 方法的調(diào)用完成了對(duì)繪制事務(wù)的提交,而在 -[_ASAsyncTransaction commit] 中會(huì)調(diào)用 notify 方法,在上一節(jié)中的 displayBlock 執(zhí)行結(jié)束后調(diào)用這里傳入的 block 執(zhí)行 -[_ASAsyncTransaction completeTransaction] 方法:

- (void)commit {
  ASDisplayNodeAssertMainThread();
  __atomic_store_n(&_state, ASAsyncTransactionStateCommitted, __ATOMIC_SEQ_CST);
    
  _group->notify(_callbackQueue, ^{
    ASDisplayNodeAssertMainThread();
    [self completeTransaction];
  });
}

我們按照時(shí)間順序來分析在上面的 block 執(zhí)行之前,方法是如何調(diào)用的钾怔,以及 block 是如何被執(zhí)行的;這就不得不回到派發(fā)繪制事務(wù)的部分了蒙挑,在 ASAsyncTransactionQueue::GroupImpl::schedule 方法中宗侦,使用了 dispatch_async 將派發(fā) block:

void ASAsyncTransactionQueue::GroupImpl::schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block) {
  ...
  if (entry._threadCount < maxThreads) {
    ...    
    dispatch_async(queue, ^{
      ...
      while (!entry._operationQueue.empty()) {
        Operation operation = entry.popNextOperation(respectPriority);
        {
          ASDN::MutexUnlocker unlock(q._mutex);
          if (operation._block) {
            operation._block();
          }
          operation._group->leave();
          operation._block = nil;
        }
      }
      ...
    });
  }
}

displayBlock 執(zhí)行之后,會(huì)調(diào)用的 groupleave 方法:

void ASAsyncTransactionQueue::GroupImpl::leave() {
  if (_pendingOperations == 0) {
    std::list<GroupNotify> notifyList;
    _notifyList.swap(notifyList);
    
    for (GroupNotify & notify : notifyList) {
      dispatch_async(notify._queue, notify._block);
    }
  }
}

這里終于執(zhí)行了在 - commit 中加入的 block忆蚀,也就是 -[_ASAsyncTransaction completeTransaction] 方法:

- (void)completeTransaction {
  if (__atomic_load_n(&_state, __ATOMIC_SEQ_CST) != ASAsyncTransactionStateComplete) {
    BOOL isCanceled = (__atomic_load_n(&_state, __ATOMIC_SEQ_CST) == ASAsyncTransactionStateCanceled);
    for (ASDisplayNodeAsyncTransactionOperation *operation in _operations) {
      [operation callAndReleaseCompletionBlock:isCanceled];
    }
    
    __atomic_store_n(&_state, ASAsyncTransactionStateComplete, __ATOMIC_SEQ_CST);
  }
}

最后的最后矾利,-[ASDisplayNodeAsyncTransactionOperation callAndReleaseCompletionBlock:] 方法執(zhí)行了回調(diào)將 displayBlock 執(zhí)行的結(jié)果傳回了 CALayer:

- (void)callAndReleaseCompletionBlock:(BOOL)canceled; {
  if (_operationCompletionBlock) {
    _operationCompletionBlock(self.value, canceled);
    self.operationCompletionBlock = nil;
  }
}

也就是在 -[ASDisplayNode(AsyncDisplay) displayAsyncLayer:asynchronously:] 方法中構(gòu)建的 completionBlock

asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled){
  ASDisplayNodeCAssertMainThread();
  if (!canceled && !isCancelledBlock()) {
    UIImage *image = (UIImage *)value;
    BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
    if (stretchable) {
      ASDisplayNodeSetupLayerContentsWithResizableImage(_layer, image);
    } else {
      _layer.contentsScale = self.contentsScale;
      _layer.contents = (id)image.CGImage;
    }
    [self didDisplayAsyncLayer:self.asyncLayer];
  }
};

這一部分進(jìn)行的大量的數(shù)據(jù)傳遞都是通過 block 進(jìn)行的,從 Runloop 中對(duì)事務(wù)的提交馋袜,以及通過 notify 方法加入的 block男旗,都是為了最后將繪制的結(jié)果傳回 CALayer 對(duì)象,而到這里可以說整個(gè) ASDK 對(duì)于視圖內(nèi)容的繪制過程就結(jié)束了欣鳖。

總結(jié)

ASDK 對(duì)于繪制過程的優(yōu)化有三部分:分別是柵格化子視圖察皇、繪制圖像以及繪制文字。

它攔截了視圖加入層級(jí)時(shí)發(fā)出的通知 - willMoveToWindow: 方法,然后手動(dòng)調(diào)用 - setNeedsDisplay什荣,強(qiáng)制所有的 CALayer 執(zhí)行 - display 更新內(nèi)容矾缓;

然后將上面的操作全部拋入了后臺(tái)的并發(fā)線程中,并在 Runloop 中注冊(cè)回調(diào)稻爬,在每次 Runloop 結(jié)束時(shí)嗜闻,對(duì)已經(jīng)完成的事務(wù)進(jìn)行 - commit,以圖片的形式直接傳回對(duì)應(yīng)的 layer.content 中桅锄,完成對(duì)內(nèi)容的更新琉雳。

從它的實(shí)現(xiàn)來看,確實(shí)從主線程移除了很多昂貴的 CPU 以及 GPU 操作友瘤,有效地加快了視圖的繪制和渲染翠肘,保證了主線程的流暢執(zhí)行。

References

其它

Github Repo:iOS-Source-Code-Analyze

Follow: Draveness · Github

Source: http://draveness.me/asdk-rendering

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市厢塘,隨后出現(xiàn)的幾起案子茶没,更是在濱河造成了極大的恐慌,老刑警劉巖晚碾,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抓半,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡格嘁,警方通過查閱死者的電腦和手機(jī)笛求,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糕簿,“玉大人探入,你說我怎么就攤上這事《” “怎么了蜂嗽?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長殃恒。 經(jīng)常有香客問我植旧,道長,這世上最難降的妖魔是什么离唐? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任病附,我火速辦了婚禮,結(jié)果婚禮上亥鬓,老公的妹妹穿的比我還像新娘完沪。我一直安慰自己,他們只是感情好嵌戈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布丽焊。 她就那樣靜靜地躺著较剃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪技健。 梳的紋絲不亂的頭發(fā)上写穴,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音雌贱,去河邊找鬼啊送。 笑死,一個(gè)胖子當(dāng)著我的面吹牛欣孤,可吹牛的內(nèi)容都是我干的馋没。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼降传,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼篷朵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起婆排,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤声旺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后段只,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腮猖,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年赞枕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澈缺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡炕婶,死狀恐怖姐赡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柠掂,我是刑警寧澤雏吭,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站陪踩,受9級(jí)特大地震影響杖们,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肩狂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一摘完、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧傻谁,春花似錦孝治、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岂座。三九已至,卻和暖如春杭措,著一層夾襖步出監(jiān)牢的瞬間费什,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來泰國打工手素, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸳址,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓泉懦,卻偏偏與公主長得像稿黍,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子崩哩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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