[譯] AsyncDisplayKit/Texture 官方文件(1)

薛定諤發表於2019-03-02

官方文件連結:texturegroup.org/docs/gettin…

開始使用 Texture

Texture 的基本單位是 NodeASDisplayNodeUIViewCALayer 的抽象,它與 UIKit 的不同在於:

Node 可以非同步繪製,且執行緒安全,你可以在在非同步執行緒中進行例項化和配置它們的層級結構。

為了保持使用者介面的流暢,你的 App 應該以 1/60 秒的幀率呈現, 這意味著主執行緒有 1/ 60 秒來處理一幀,也就是說,主執行緒需要在 16 毫秒內來執行所有的佈局和繪圖程式碼,而由於一些系統級別的開銷,你的佈局繪圖程式碼一般執行超過 10 毫秒,就可能引起掉幀。

Texture 可以讓你將影像解碼、文字渲染和其他消耗效能的 UI 操作從主執行緒剝離,以保證主執行緒可以流暢的響應使用者的互動,Texture 還有其他的使用竅門,我們稍後會講到。

節點/Node

如果你會使用 UIView,那麼你就已經知道如何使用 Node 了,UIView 中絕大部分的方法 Node 都有對映,並且 UIViewCALayer 大多數的屬性,也是可以使用的。當屬性和方法的命名有差異時,例如 .clipsToBounds.masksToBoundsNode 預設使用 UIView 的命名,唯一例外的是 Node 使用 .position 替代了 .center

當然,你也可以通過 node.viewnode.layer 直接呼叫原生屬性和方法,但要確保它會在主執行緒上執行!

Texture 已經提供了多種多樣的 Node 來替換你習慣使用的大部分 UIKit 元件,現在你已經可以完全通過 Texture 開發大規模的 App 。

節點容器/Node Containers

當你將 Texture 整合到一個專案中時,一個常見的錯誤是將 Node 直接新增到已有的檢視中,這樣做的結果是你的節點在渲染時會閃爍。

正確的做法是,你應該把節點新增到 Node 容器中,由 Node 容器負責管理這些節點,你可以把 Node 容器理解為 UIKit 和 ASDK 之間的橋樑。

佈局引擎/Layout Engine

Texture 的佈局非常強大,相對於傳統的 Frame,AutoLayout 等方式而言比較獨特,但對前端工作者並不陌生,Texture 的佈局基於 CSS FlexBox。它提供了指定自定義節點的大小和其子節點佈局的宣告方式,當所有的節點同時被渲染時,通過為每個節點提供的 ASLayoutSpec 非同步計算 size 和佈局。

它和 UIStackView 很像,但支援低版本 iOS。

高階開發者功能

Texture 提供了在 UIKit 或 Foundation 中無法找到的各種高階開發功能,我們的開發人員發現,Texture 可以簡化它們的架構,從而提高了開發效率。

完整列表即將推出…

沒放出來說個啥?

整合 Texture

如果你第一次使用 Texture,我們建議你看看 ASDKgram 示例,我們已經撰寫了一個 Guide(即將推出),指導你如何一步一步將 Texture 整合到一個 App 中。

沒放出來說個啥?

如果您遇到任何問題,請到我們的 GitHub 或 Slack 尋求幫助。

資源

在 Slack 社群,和 Texture 的核心團隊及 700+ Texture 開發者一起,實時 Debug、獲取更新和進行交流,點此註冊

示例

檢視我們的示例庫,如果你第一次使用 Texture,我們建議你從 ASDKgram 開始,這個示例同時使用 UIKit 和 Texture 實現了照片Feed,你可以對比檢視它們的區別,它的特點是:

  1. 一個無限滾動的 Home Feed,演示 Texture 的平滑滾動效能;
  2. 一個相當龐大的程式碼庫,用於演示使用 Texture 設計 App 可以減少多少程式碼;

視訊

教程/文章

FlexBox 佈局學習

Texture 強大的佈局系統基於CSS FlexBox 模型,這些網站對於學習 FlexBox 的基本知識非常有用。

安裝 Texture

Texture 可以使用 CocoaPods 和 Carthage 安裝,使用時不要忘記匯入標頭檔案:

#import <AsyncDisplayKit/AsyncDisplayKit.h>複製程式碼

如果你使用 Swift 開發,可以使用 Objective-C bridging header 進行橋接,如果你在安裝中遇到任何問題,請在 GitHub 或 Slack 聯絡我們。

CocoaPods

使用 CocoaPods 安裝,將以下內容新增到 Podfile:

target `MyApp` do
    pod "Texture"
end複製程式碼

完全退出 Xcode,開啟終端,cd 到專案目錄,執行下面的命令:

pod install複製程式碼

如果要更新 Texture,開啟終端,cd 到專案目錄,執行下面的命令:

pod update Texture複製程式碼

不要忘記使用 .xcworkspace 開啟,而不是 .xcodeproj

Carthage(常規安裝)

使用迦太基需要建立一個Cartfile 列表,然後執行 carthage update,將依賴項下載到 Cathage/Checkouts 資料夾中,並將它們構建到位於 Carthage/Build 資料夾中,開發人員須手工整合到專案中。

Texture 也可以通過迦太基安裝,將以下內容新增到 Cartfile 以獲取最新的 release 版本:

github "texturegroup/texture"複製程式碼

或者獲取主幹:

github "texturegroup/texture" "master"複製程式碼

Texture 有自己的 Cartfile,指明瞭自己的依賴項,Texture 所需的依賴會自動安裝,安裝 Texture 依賴需要使用終端,在Carthage/Checkouts 目錄下,執行:

carthage update複製程式碼

確認 Texture、PINRemoteImage (3.0.0-beta.2)、PINCache 全部獲取和構建,Texture 的 Cartfile 會自動處理這些依賴關係。

開啟 Xcode,將所需的框架拖到 TARGETS - General -Linked Frameworks and Libraries

Carthage(light)

Texture 不支援 Carthage 更輕量的使用方式,你需要手動新增專案檔案, 這是因為 Texture 的依賴 PINCache 還沒有專案檔案,

PINCache 是 PINRemoteImage 一個巢狀的依賴。

如果沒有 PINRemoteImage 和 PINCache,你將無法完整的使用 Texture 影像功能集。

升級到 2.0

釋出說明

請閱讀 GitHub 上的官方釋出說明。

獲取正式候選版本

將以下內容新增到你的 pod 檔案中:

pod `Texture`, `>= 2.0`複製程式碼

在終端執行:

pod repo update
pod update Texture複製程式碼

測試 2.0

一旦你更新到2.0,你會看到許多棄用警告。 別擔心!這些警告是安全的,因為我們已經橋接了所有舊的 API,以便在遷移到新的 API 之前對 2.0 進行測試。
如果你的 App 無法構建成功而不是僅顯示警告,那麼你的專案中可能出現錯誤,你有幾個選擇:

  1. 在 project settings 中禁用 deprecation warnings;
  2. 在專案的 build settings 中禁用 warnings as errors;
  3. 在 Texture 中禁用 deprecation warnings,這需要你將 ASBaseDefines.h 中的第 74 行更改為 # define ASDISPLAYNODE_WARN_DEPRECATED 0

當你的 App 構建成功並執行,你需要測試它以確保一切正常工作。 如果你發現任何問題,請嘗試在該區域採用新的 API 並重新測試。

你可能會注意到一個關鍵的變化:

ASStackLayoutSpec的.alignItems 屬性預設值更改為 ASStackLayoutAlignItemsStretch 而不是 ASStackLayoutAlignItemsStart,這可能會在 UI 中造成失真。

如果還有其他問題,請提交 GitHub issue,我們很樂意幫助你!

聰明的預載入

非同步併發渲染和 FlexBox 佈局已經非常強大,但 Texture 做到的不止於此,另外一個重要的層面是智慧預載入的思想。

正如在開始使用 Texture 這節中講的那樣,在一個節點容器的上下文之外使用一個節點有一些弊端的。這是因為所有的節點都具有當前介面狀態的概念,這個狀態命名為 interfaceState

interfaceState 這個屬性是不斷更新的,它的更新由 ASRangeController 所控制,ASRangeController 又由所有的節點容器在內部建立和維護。

在容器外部使用的節點不會被 ASRangeController 更新狀態,因此這有時會導致閃爍,原因是這些容器外的節點因為狀態的錯誤,在節點被渲染到螢幕後又進行了一次渲染。

Interface State Ranges

當將節點新增到滾動或分頁介面時,它們通常位於以下範圍中的一個。這意味著當滾動檢視被滾動時,它們的介面狀態將隨著它們的移動而更新。

一個節點的所處的範圍會是以下範圍中的一個:

介面範圍 描述
Preload 節點還不可見,這時節點收集外部源,外部源可能是 API 或者本地磁碟。
Display 節點開始渲染,包括文字的光柵化已經影像解碼等。
Visible 節點可見,在螢幕上至少擁有一個畫素。

ASRangeTuningParameters

每個範圍的大小以整個螢幕的尺寸作為參照, 預設的 size 在許多用例中都能很好地工作,你也可以通過在滾動節點上設定範圍引數來調整它們。

在上面的一個滾動集合的示例圖片中,使用者正在向下滾動。正如你所看到的,使用者滾動方向區域(領先方向)要比使用者離開方向區域(尾隨方向)大得多。為了保持記憶體的最佳使用,當使用者改變滾動方向時,兩個區域會動態地交換。這使你不必考慮滾動方向的變化,只需關注領先方向和尾隨方向的區域大小。

在這張圖中,你可以看到智慧的預載入是如何進行工作的,你可以看到在一個垂直的滾動容器中,雖然有些節點還未在裝置螢幕中出現,但是它有一個範圍控制器,螢幕外的節點處在 Preload 資料準備範圍和 Display 渲染準備範圍。

Interface State Callbacks

當使用者滾動時,節點在這三個範圍中切換,並通過載入資料,渲染等作出適當的反應。你建立的節點子類可以通過實現相應的回撥方法進入此機制。

class TZYNode: ASDisplayNode {

    override func didEnterPreloadState() {
        print("進入資料載入範圍")
    }

    override func didExitPreloadState() {
        print("離開資料載入範圍")
    }

    override func didEnterDisplayState() {
        print("進入渲染範圍")
    }

    override func didExitDisplayState() {
        print("離開渲染範圍")
    }

    override func didEnterVisibleState() {
        print("進入可見範圍")
    }

    override func didExitVisibleState() {
        print("離開可見範圍")
    }

}複製程式碼

節點容器/Node Containers

在節點容器中使用節點

我們強烈建議你在節點容器中使用 Texture 的節點, Texture 提供以下節點容器:

節點容器 等價於 UIKit
ASCollectionNode 代替 UICollectionView
ASPagerNode 代替UIPageViewController
ASTableNode 代替UITableView
ASViewController 代替UIViewController
ASNavigationController 代替UINavigationController,實現 ASVisibility 協議。
ASTabBarController 代替UITabBarController,實現 ASVisibility 協議。

示例程式碼和特定示例專案會在每個節點容器的文件中突出顯示。

為什麼使用節點容器

節點容器自動管理其子節點實現智慧預載入,這意味著節點的所有佈局計算,資料讀取,解碼和渲染都將會非同步完成,這就是為什麼我們建議將節點放進節點容器中使用的原因。
請注意,儘管你可以直接使用節點而不加入節點容器,但除非你新增其他回撥,否則這個容器外的節點只會在螢幕出現時才會開始渲染。如同 UIKit 所做的那樣,這可能導致效能下降和內容閃爍。

節點子類/Node Subclasses

在 UIKit 元件上使用節點的一個主要優點是,所有的節點都預先在主執行緒佈局並繪製,這樣主執行緒就可以立即響應使用者互動事件,而無需先處理控制元件的渲染,Texture 提供以下節點:

節點 等價於 UIKit
ASDisplayNode 代替 UIView,所有的 Node 都繼承自 ASDisplayNode。
ASCellNode 代替 UITableViewCell&UICollectionViewCell,需要和 ASTableNode,ASCollectionNode 和 ASPagerNode 共同使用。
ASScrollNode 代替 UIScrollView,這個節點對於建立自定義的,包含其他節點的可滾動區域非常有用。
ASEditableTextNode 代替 UITextView。
ASTextNode 代替 UILabel。
ASImageNode 代替 UIImage。
ASNetworkImageNode 代替 UIImage。
ASMultiplexImageNode 代替 UIImage。
ASVideoNode 代替 AVPlayerLayer。
ASVideoPlayerNode 代替 UIMoviePlayer。
ASControlNode 代替 UIControl。
ASButtonNode 代替 UIButton。
ASMapNode 代替 MKMapView。

儘管與 UIKit 元件大致相當,但一般而言,Texture 節點提供了更高階的功能和便利。 例如,ASNetworkImageNode 可以自動載入網路圖片和進行快取管理,甚至支援漸進式 JPEG 和動畫 GIF。

AsyncDisplayKitOverview 示例應用程式給出了上面列出的每個節點的基本實現。

節點繼承關係/Node Inheritance Hierarchy

所有的 Texture 節點都繼承自 ASDisplayNode

Node層級
Node層級

原圖使用花體英文,檢視原圖

右側的節點是 UIKit 元素的封裝。 例如,ASScrollNode 封裝了一個 UIScrollView,而 ASCollectionNode 封裝了一個 UICollectionViewliveMapMode 中的 ASMapNodeUIMapView 的封裝。

liveMapMode 未在此節和之前章節出現,不清楚是否為筆誤。

節點例項/Subclassing

建立子類時最重要的區別是你使用的是 ASViewController 還是 ASDisplayNode,這聽起來很明顯,但是因為這其中有一些微妙的差異,所以記住這點還是相當重要的。

ASDisplayNode

雖然例項化節點與 UIView 類似,但需要遵循一些原則,以確保你充分利用了它的能力,並確保節點按照預期的方式執行。

-init

在使用 initNodeBlocks 時,這個方法會後臺執行緒上被呼叫。但是,因為沒有其他方法會在 init 完成之前執行,所以這個方法不需要加鎖。

需要記住的最重要的一點是,init 方法必須能夠在任何佇列上呼叫。最值得注意的是,你永遠不應該在節點初始化方法中初始化任何 UIKit 物件,以及呼叫 node.layer node.view.x 等與viewlayer 有關的操作,也不應該在這個方法中為節點新增手勢,這些事件應該在 didLoad 方法中進行。

-didLoad

這個方法在概念上類似於 UIViewController-viewDidLoad 方法,當後臺檢視初始化完成時,它會被呼叫一次,它保證會在主執行緒上被呼叫,是執行任何 UIKit 程式碼合適的地方,例如新增手勢識,更改 viewlayer,初始化 UIKit 物件。

-layoutSpecThatFits:

該方法定義了節點的佈局,並在後臺執行緒上進行了大量的計算。此方法是你宣告、建立和修改 ASLayoutSpec 佈局描述物件的地方,該物件描述了節點的 size,以及其子節點的 size 和 position,是你放置大部分佈局程式碼的地方。

ASLayoutSpec 物件直到在此方法中返回前是可變的。 在這之後,這個物件將不可改變,需要注意的是你不需要快取 ASLayoutSpec 物件以備後用,我們建議你在必要時重新建立佈局描述。

由於它在後臺執行緒上執行,因此你不能在這個方法中呼叫 node.viewnode.layer 以及它們的屬性。 此外,除非你明確知道自己在做什麼,否則不要在此方法中建立其他節點。 另外,重寫此方法並不一定需要呼叫 super 方法。

-layout

在此方法中呼叫 super 將,會使用 layoutSpec 物件計算佈局,所有子節點都將計算其 size 和 position。

-layout 在概念上類似於 UIViewController-viewwilllayoutsubview,這是一個更改 hidden 屬性、修改 view 屬性、設定背景顏色的好地方。你可以在 -layoutspec: 方法中設定背景顏色,但這可能會存在時序問題。如果你需要使用原生的 UIView,可以在這裡設定它們的 frame,不管怎樣,你始終可以使用 -initWithViewBlock: 建立節點,並在其他地方的後臺執行緒中進行調整。

這個方法在主執行緒上被呼叫,如果你使用的是 ASLayoutSpec,那麼你不應該過多地依賴這個方法,因為在主執行緒上進行佈局是非常可取的,需要這個方法的子類小於 1/10。

使用 -layout 的一個重要用途是你需要子節點的 size 是精確的。舉例來說,當你希望一個 collectionNode 可以鋪面螢幕,這種情況不被 ASLayoutSpec 很好的支援,此時最簡單的做法是在這個方法中手動設定 frame

subnode.frame = self.bounds複製程式碼

如果你希望在 ASViewController 中得到相同的效果,那麼你可以在 -viewWillLayoutSubviews 中做同樣的事情,不過如果你的節點通過 initWithNode: 進行例項化,它會自動做到這一點。

ASViewController

ASViewController 是一個常規的 UIViewController 子類,它具有管理節點的特殊功能。因為它是一個 UIViewController 子類,所以所有的方法都在主執行緒上被呼叫,並且你應該在主執行緒上建立至少一個 ASViewController

-init

這個方法在 ASViewController 的生命週期開始時被呼叫一次,與 UIViewController 的初始化一樣,你最好不要在這個方法中訪問 self.viewself.node.view,因為這樣會強制建立 view。 這些操作可以在 -viewDidLoad 中進行,-viewDidLoad 可以執行任何 view 的訪問。

ASViewController 指定的構造器是 initWithNode:,一個典型的構造器看起來就像下面的程式碼。請注意下面的程式碼,在呼叫 super 之前,ASViewController 的節點是如何被建立的,ASViewController 管理節點類似於 UIViewController 管理檢視,但是它的初始化方式有所區別:

class TZYVC: ASViewController<ASDisplayNode> {

    init() {
        let pagerNode = ASPagerNode()
        super.init(node: pagerNode)
        pagerNode.setDataSource(self)
        pagerNode.setDelegate(self)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

extension TZYVC: ASPagerDataSource {

    func numberOfPages(in pagerNode: ASPagerNode) -> Int {
        return 10
    }

}

extension TZYVC: ASPagerDelegate {


}複製程式碼

-loadView

我們建議你不要使用這個方法,因為與 -viewDidLoad 相比,它沒有什麼特別的優勢,並且有一些缺點。 但是,只要不將 self.view 屬性設定為不同的值,它可以安全的使用。 它的 super 方法會將其封裝的 UIViewControllerview 設定為 ASViewControllernode.view

-viewDidLoad

這個方法在 -loadView 之後被執行,這是 ASViewController 生命週期中,你可以訪問 node.view 最早的方法,你可以在這份方法中任意修改 viewlayer 或新增手勢,這個方法在其所屬的生命週期中,只會執行一次。

所以佈局程式碼不應該放在這個方法中,因為當介面重繪時,這裡的程式碼不會被再次呼叫。UIViewController 中這個方法也是同樣的,在這種方法中放置佈局程式碼是一種不太好的做法,即使你的佈局不會因為互動發生變化。

-viewWillLayoutSubviews

這個方法會與節點的 -layout 同時呼叫,它可能在 ASViewController 的生命週期中被多次呼叫,當 ASViewController 的節點的邊界發生改變,如旋轉、分割螢幕、鍵盤彈出等行為,或者當檢視的層次結構發生變化,如子節點新增、刪除或改變大小時,這個方法將被呼叫。

因為它不經常被呼叫,但是呼叫就代表頁面需要重繪,因此所有的佈局程式碼最好都放在這個方法中,即使是不直接依賴於 size 的 UI 程式碼也應放在這裡。

-viewWillAppear: / -viewDidDisappear:

viewWillAppearASViewController 的節點出現在螢幕上之前被呼叫,這是節點從螢幕出現的最早時間,viewDidDisappear 在控制器從檢視層次結構中移除之後被呼叫,這是節點從螢幕消失的最早時機,這兩個方法提供了一個很好的時機來啟動或停止與控制器相關的動畫,這也是一個儲存和記錄使用者行為日誌的好地方。

儘管這些方法可能被多次呼叫,並且是可以執行佈局程式碼的,但是這兩個方法不會在所有需要重繪的時候被呼叫,因此除了特定的動畫設定之外,不應該用於執行核心的佈局程式碼。

未完待續

[譯] AsyncDisplayKit/Texture 官方文件(2)

相關文章