AsyncDisplayKit的使用
一、簡介
AsyncDisplayKit 是一個UI框架,最初誕生于 Facebook 的 Paper 應用程序。它是為了解決 Paper 團隊面臨的核心問題之一:盡可能緩解主線程的壓力。它能在異步線程繪制修改UI,然后統一添加進內存渲染出來。
ASDK 的作者是 Scott Goodson (Linkedin),
他曾經在蘋果工作,負責 iOS 的一些內置應用的開發,比如股票、計算器、地圖、鐘表、設置、Safari 等,當然他也參與了 UIKit framework 的開發。后來他加入 Facebook 后,負責 Paper 的開發,創建并開源了 AsyncDisplayKit。目前他在 Pinterest 和 Instagram 負責 iOS 開發和用戶體驗的提升等工作。
1.解決的問題
很多時候用戶在操作app的時候,會感覺到不適那么流暢,有所卡頓。
ASDK主要就是解決的問題就是操作頁面過程中的保持幀率在60fps(理想狀態下)的問題。
造成卡頓的原因有很多,總結一句話基本上就是:
CPU或GPU消耗過大,導致在一次同步信號之間沒有準備完成,沒有內容提交,導致掉幀的問題。
具體的原理,在提升 iOS 界面的渲染性能文章中介紹的十分詳細了,這里也不多闡述了。
2.優化原理
- 布局:
iOS自帶的Autolayout在布局性能上存在瓶頸,并且只能在主線程進行計算。(參考Auto Layout Performance on iOS)因此ASDK棄用了Autolayout,自己參考自家的ComponentKit設計了一套布局方式。 - 渲染
對于大量文本,圖片等的渲染,UIKit組件只能在主線程并且可能會造成GPU繪制的資源緊張。ASDK使用了一些方法,比如圖層的預混合等,并且異步的在后臺繪制圖層,不阻塞主線程的運行。 - 系統對象創建與銷毀
UIKit組件封裝了CALayer圖層的對象,在創建、調整、銷毀的時候,都會在主線程消耗資源。ASDK自己設計了一套Node機制,也能夠調用。
實際上,從上面的一些解釋也可以看出,ASDK最大的特點就是"異步"。
將消耗時間的渲染、圖片解碼、布局以及其它 UI 操作等等全部移出主線程,這樣主線程就可以對用戶的操作及時做出反應,來達到流暢運行的目的。
ASDK 認為,阻塞主線程的任務,主要分為上面這三大類。
為了盡量優化性能,ASDK 嘗試對 UIKit 組件進行封裝:
3.Nodes節點
如果你之前使用過views,那么你應該已經知道如何使用nodes,大部分的方法都有一個等效的node,大部分的UIView和CALayer的屬性都有類似的可用的。任何情況都會有一點點命名差異(例如,clipsToBounds和masksToBounds),node基本上都是默認使用UIView的名字,唯一的例外是node使用position而不是center
當然,你也可以直接訪問底層view和layer,使用node.view和node.layer
-
關系圖
image
這是常見的 UIView 和 CALayer 的關系:View 持有 Layer 用于顯示,View 響應觸摸事件。
- Node控件
ASDK | UIKit |
---|---|
ASDisplayNode | UIView |
ASCellNode | UITableViewCell/UICollectionViewCell |
ASTextNode | UILabel |
ASImageNode/ASNetworkImageNode | UIImageView |
ASVideoNode | AVPlayerLayer |
ASControlNode | UIControl |
ASScrollNode | UIScrollView |
ASEditableTextNode | UITextView |
ASMultiplexImageNode(圖片管理) | UIImageView |
4.安裝
CocoaPods安裝
pod 'AsyncDisplayKit'
Carthage安裝
AsyncDisplayKit可以使用Carthage安裝,將下面的代碼添加進入Cartfile
github "facebook/AsyncDisplayKit"
在終端執行carthage update來構建AsyncDisplayKit庫,會自動在項目根目錄下生成Carthage名字的文件夾,里面有個build文件夾,可以用來framework到你打算使用的項目中
靜態庫
AsyncDisplayKit可以當做靜態庫引入
- 拷貝整個工程到你的目錄下,添加AsyncDisplayKit.xcodeproj到你的workspace
- 在build phases中,在Target Dependencies下添加AsyncDisplayKit Library
- 在build phases中,添加libAsyncDisplayKit.a, AssetsLibrary, Photos等框架到Link Binary With Libraries中
- 在build settings中,添加-lc++和-ObjC到 project linker flags
二、使用
主要介紹常用控件ASTableNode/ASCollectionNode的使用,代碼放在GitHub上的ASDK_Demo。
1.ASImageNode
- 使用ASNetworkImageNode的URL設置網絡圖片。
- ASNetworkImageNode有圖片下載的ASNetworkImageNodeDelegate
- ASImageNode使用ASDK的圖片管理類PINCache,PINRemoteImage
- 如果不打算引入PINRemoteImage和PINCache,你會失去對jpeg的更好的支持,你需要自行引入你自己的cache系統,需要遵從ASImageCacheProtocol
2.ASTextNode
ASTextNode沒有text屬性,只能使用attributedText
//居中
NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = NSTextAlignmentCenter;
labelNode.attributedText = [[NSAttributedString alloc] initWithString:@"居中文字" attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:18],
NSForegroundColorAttributeName:[UIColor blackColor],
NSBackgroundColorAttributeName : [UIColor clearColor],
NSParagraphStyleAttributeName:paragraphStyle}];
3.ASTableNode/ASCollectionNode
- ASTableNode/ASCollectionNode不支持復用機制,每次滾動都會重新創建cell。
- ASTableNode并不提供類似UITableview的-tableView:heightForRowAtIndexPath:方法,這是因為節點基于自己的約束來確定自己的高度,就是說你不再需要寫代碼來確定這個細節,一個node通過-layoutSpecThatFits:方法返回的布局規則確定了行高,所有的節點只要提供了約束大小,就有能力自己確定自己的尺寸
- 使用 Batch Fetching 進行無限滾動,即預加載功能
三、布局
ASDK 擁有自己的一套成熟布局方案,雖然學習成本略高,但至少比原生的 AutoLayout 寫起來舒服,重點是性能比起 AutoLayout 好的不是一點點。(ASDK不支持autoLayout)
//下面這個方法就是用來建立布局規則對象,產生 node 大小以及所有子 node 大小的地方,你創建的布局規則對象一直持續到這個方法返回的時間點,經過了這個時間點后,它就不可變了。尤其重要要記住的一點事,千萬不要緩存布局規則對象,當你以后需要他的時候,請重新創建。
//調用時機:ASDisplayNode 在初始化之后會檢查是否有子視圖,如果有就會調用
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
1. 布局類
- ASAbsoluteLayoutSpec(絕對布局約束)
- ASBackgroundLayoutSpec(背景布局規則)
- ASInsetLayoutSpec(邊距布局規則)
- ASOverlayLayoutSpec(覆蓋布局規則)
- ASRatioLayoutSpec(比例布局規則)
- ASRelativeLayoutSpec(相對布局規則)
- ASCenterLayoutSpec(中心布局規則)
- ASStackLayoutSpec(堆疊布局規則)
- ASWrapperLayoutSpec (填充布局規則)
2.示例
ASAbsoluteLayoutSpec
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
self.childNode.style.layoutPosition = CGPointMake(100, 100);
self.childNode.style.preferredLayoutSize = ASLayoutSizeMake(ASDimensionMake(100), ASDimensionMake(100));
ASAbsoluteLayoutSpec *absoluteLayout = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[self.childNode]];
return absoluteLayout;
}
使用方法和原生的絕對布局類似
ASBackgroundLayoutSpec
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
ASBackgroundLayoutSpec *backgroundLayout = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:self.childNodeB background:self.childNodeA];
return backgroundLayout;
}
把childNodeA 做為 childNodeB 的背景,也就是 childNodeB 在上層,要注意的是 ASBackgroundLayoutSpec 事實上根本不會改變視圖的層級關系
ASInsetLayoutSpec
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
ASInsetLayoutSpec *inset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:_childNode];
return insetLayout;
}
_childNode 相對于父視圖邊距都為 0,相當于填充整個父視圖。
ASOverlayLayoutSpec
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
_photoNode.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT*2, USER_IMAGE_HEIGHT*2);
// INIFINITY(插入無邊界)
UIEdgeInsets insets = UIEdgeInsetsMake(INFINITY, 12, 12, 12);
ASInsetLayoutSpec *textInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_titleNode];
return [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_photoNode
overlay:textInsetSpec];
}
類似于ASBackgroundLayoutSpec,都是設置層級關系
ASRatioLayoutSpec
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
ASRatioLayoutSpec *ratioLayout = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1.0f child:self.childNodeA];
return ratioLayout;
}
比較常用的一個類,作用是設置自身的高寬比,例如設置正方形的視圖
ASRelativeLayoutSpec
//把 childNodeA 顯示在右上角。
self.childNodeA.style.preferredSize = CGSizeMake(100, 100);
ASRelativeLayoutSpec *relativeLayout = [ASRelativeLayoutSpec relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionEnd verticalPosition:ASRelativeLayoutSpecPositionStart sizingOption:ASRelativeLayoutSpecSizingOptionDefault child:self.childNodeA];
return relativeLayout;
}
它可以把視圖布局在:左上、左下、右上、右下四個頂點以外,還可以設置成居中布局。
ASCenterLayoutSpec
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
self.childNodeA.style.preferredSize = CGSizeMake(100, 100);
ASCenterLayoutSpec *relativeLayout = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:self.childNodeA];
return relativeLayout;
}
ASWrapperLayoutSpec
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
ASWrapperLayoutSpec *wrapperLayout = [ASWrapperLayoutSpec wrapperWithLayoutElement:self.childNodeA];
return wrapperLayout;
}
填充整個視圖
ASStackLayoutSpec
可以說這是最常用的類,而且相對于其他類來說在功能上是最接近于 AutoLayout 的。
之所以稱之為盒子布局是因為它和 CSS 中 Flexbox 很相似,不清楚 Flexbox 的可以看下這篇博客(HTML布局)。
示例
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
// 當用戶名和位置信息文本太長時,收縮堆放視圖來適應屏幕,而不是將所有內容向右堆放
ASStackLayoutSpec *nameLocationStack = [ASStackLayoutSpec verticalStackLayoutSpec];
nameLocationStack.style.flexShrink = 1.0;
nameLocationStack.style.flexGrow = 1.0;
//如果從服務器獲取位置信息,并檢查位置信息是否可用
if (_postLocationNode.attributedText) {
nameLocationStack.children = @[_usernameNode, _postLocationNode];
} else {
nameLocationStack.children = @[_usernameNode];
}
//水平堆放
ASStackLayoutSpec *headerStackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
spacing:40
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsCenter
children:@[nameLocationStack, _postTimeNode]];
//插入堆放
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 10, 0, 10)
child:headerStackSpec];
}
簡單的說明下各個參數的作用:
1.direction
:主軸的方向,有兩個可選值:
- 縱向:
ASStackLayoutDirectionVertical
(默認)- 橫向:
ASStackLayoutDirectionHorizontal
2.spacing
: 主軸上視圖排列的間距,比如有四個視圖,那么它們之間的存在三個間距值都應該是spacing
3.justifyContent
: 主軸上的排列方式,有五個可選值:ASStackLayoutJustifyContentStart
從前往后排列ASStackLayoutJustifyContentCenter
居中排列ASStackLayoutJustifyContentEnd
從后往前排列ASStackLayoutJustifyContentSpaceBetween
間隔排列,兩端無間隔ASStackLayoutJustifyContentSpaceAround
間隔排列,兩端有間隔
4.alignItems
: 交叉軸上的排列方式,有五個可選值:ASStackLayoutAlignItemsStart
從前往后排列ASStackLayoutAlignItemsEnd
從后往前排列ASStackLayoutAlignItemsCenter
居中排列ASStackLayoutAlignItemsStretch
拉伸排列ASStackLayoutAlignItemsBaselineFirst
以第一個文字元素基線排列(主軸是橫向才可用)ASStackLayoutAlignItemsBaselineLast
以最后一個文字元素基線排列(主軸是橫向才可用)
5.children
: 包含的視圖。數組內元素順序同樣代表著布局時排列的順序
四、優缺點
- 不支持大家常用的storyboard、xib、autoLayout,影響開發效率
- 代碼沒有UIKit使用熟練
- 網上資源少
- 但是可以和UIKit混合開發