APP性能的優化,一直都是任重而道遠,對於現在須要承載更多信息的APP來講更是突出,值得慶幸的蘋果在這方面作得至少比安卓讓開發者省心。UIKit 控件雖然在大多數狀況下都能知足用戶對於流暢性的需求,但有時候仍是難以達到理想效果。html
AsyncDisplayKit(如下簡稱ASDK) 的出現至少又給了開發者一個不錯的選擇。畢竟Paper(雖然 Facebook 已經關閉了這個應用)當年有着炫酷的效果的同時依然保持較好的流暢性也得益於 ASDK 的加入。在Paper發佈的幾個月後 Facebook 就乾脆從中剝離出來成爲一個獨立的庫,就在前兩天 ASDK 恰好發佈了 2.0 版本。node
目前據我所知國內比較知名有 輕芒閱讀(豌豆莢一覽) 、 即刻 和 Yep 在用ASDK。
拿 即刻 來講包括 消息盒子
、主題的詳情頁
、動態通知
、個人喜歡
、評論頁
、最近熱門
、即刻小報
、他關注的人
、關注他的人
以及搜索頁
都用到了 ADSK。git
目前 AsyncDisplayKit 已經從 facebook 遷移至 TextureGroup 新的項目地址是 Texturegithub
Texture 幾乎涵蓋了經常使用的控件,下面是 Texture
和 UIKit
的對應關係,有些封裝能夠說很是良心。objective-c
Nodes:api
Texture | UIKit |
---|---|
ASDisplayNode | UIView |
ASCellNode | UITableViewCell/UICollectionViewCell |
ASTextNode | UILabel |
ASImageNode | UIImageView |
ASNetworkImageNode | UIImageView |
ASVideoNode | AVPlayerLayer |
ASControlNode | UIControl |
ASScrollNode | UIScrollView |
ASControlNode | UIControl |
ASEditableTextNode | UITextView |
ASMultiplexImageNode | UIImageView |
Node Containers網絡
Texture | UIKit |
---|---|
ASViewController | UIViewController |
ASTableNode | UITableView |
ASCollectionNode | UICollectionView |
ASPagerNode | UICollectionView |
子父類關係:app
ASDisplayNodeasync
ASCellNodeide
ASCollectionNode
ASControlNode
ASImageNode
ASNetworkImageNode
做用同等於UIView
,是全部 Node 的父類,須要注意的是 ASDisplayNode
其實擁有一個view
屬性,因此ASDisplayNode
及其子類均可以經過這個view
來添加UIKit
控件,這樣一來 Texture
和 UIKit
混用是徹底沒問題的。
ASDisplayNode
中添加 UIKit
UIView *otherView = [[UIView alloc] init]; otherView.frame = ...; [self.view addSubview:otherView];
或
ASDisplayNode *gradientNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{ UIView *view = [[UIView alloc] init]; return view; }];
第二種的初始化最終生成的就是 block 返回的 UIKit
對象,但外部表現出來的是 ASDisplayNode
。這樣子的好處在於佈局,關於佈局,後面會講到。
UIKit
中添加 ASDisplayNode
ASImageNode *imageNode = [[ASImageNode alloc] init]; imageNode.image = [UIImage imageNamed:@"iconShowMore"]; imageNode.frame = ...; [self addSubnode:imageNode]; self.imageNode = imageNode;
做用同等於 UITableViewCell
或 UICollectionViewCell
,自帶 indexPath
屬性,有些時候頗有用。
做用同等於UILabel
,和 UILabel
不一樣的是 ASTextNode
必須經過 attributedText
添加文字。
在 ASTextNode 基礎修復了一些 Bug
做用同等於 UIImageView
,可是隻能設置靜態圖片,若是須要使用網絡圖片,請使用 ASNetworkImageNode
。
做用同等於 UIImageView
,若是使用網絡圖片請使用此類,Texture
用的是第三方的圖片加載庫PINRemoteImage,ASNetworkImageNode
其實並不支持 gif,若是須要顯示 gif 推薦使用FLAnimatedImage。
做用同等於 UIButton
,須要注意的是下面這個兩個屬性
@property (nonatomic, assign) CGFloat contentSpacing;// 設置圖片和文字的間距 @property (nonatomic, assign) ASButtonNodeImageAlignment imageAlignment;// 圖片和文字的排列方式,
簡直要抱頭痛哭一下😭,imageAlignment
能夠設置兩個值:
ASButtonNodeImageAlignmentBeginning, // 圖片在前,文字在後 ASButtonNodeImageAlignmentEnd// 文字在前,圖片在後
做用同等於 UITableView
,可是實現上並無採用 UITableView
的重用機制,而是經過用戶滾動對須要顯示的視圖進行add
和 不須要的進行remove
的操做(我猜的)。另外重要的一點:ASTableNode
並無像 UITableView
同樣提供一個-tableView:heightForRowAtIndexPath:
協議方法來決定每一個 Cell 的高度,而是由 ASCellNode
自己決定。這樣帶來的另一個好處是,動態高度的實現可謂是易如反掌,具體能夠看官方 Demo 中的 Kittens。
對於現有的項目中出現的並不嚴重的性能問題,個人建議是用對應的 Texture 控件代替便可。
好比把 UIImageView -> ASImageNode/ASNetworkImageNode
,UILabel -> ASTextNode
之類的,而不是把原有的 UITableView -> ASTableNode
,UICollectionView -> ASCollectionNode
。
在 Cell 中替換 UIImageView
ASImageNode *imageNode = [[ASImageNode alloc] init]; imageNode.image = [UIImage imageNamed:@"iconShowMore"]; imageNode.frame = ...; [self.contentView addSubnode:imageNode]; self.imageNode = imageNode;
緣由有如下幾點:
ASCellNode
內部的佈局會用到 Texture 自己有一套佈局方案,然而這套佈局學習成本略高。ASTableNode
和 ASCollectionNode
和原生的 UITableView
和 UICollectionView
有較大的 API 改變,侵略性較大,不太利於後期維護。ASTableNode
和 ASCollectionNode
的支持仍是有點問題。因此當你尚未作好應付上面三個問題的準備,簡單的 UIKit -> Texture
替換纔是正確選擇。
閱讀 Texture 佈局篇
刷新列表
不管是 ASTableNode
仍是 ASCollectionNode
當列表中已經有數據顯示了,調用 reloadData
你會發現列表會閃一下。最多見的案例是上拉加載更多獲取到新數據後調用 reloadData
刷新列表用戶體驗會比較差,事實上官方文檔在 [Batch Fetching API] 給出瞭解決辦法:
- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context { // Fetch data most of the time asynchronoulsy from an API or local database NSArray *newPhotos = [SomeSource getNewPhotos]; // Insert data into table or collection node [self insertNewRowsInTableNode:newPhotos]; // Decide if it's still necessary to trigger more batch fetches in the future _stillDataToFetch = ...; // Properly finish the batch fetch [context completeBatchFetching:YES]; }
獲取新數據後直接插入到列表中,而不是刷新整個列表,好比:
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
和
- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
加載數據
細心的同窗可能發現了前面提到內容中的就有相關的方法:
- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context;
如今絕大多數APP加載更多數據的方法都是經過下拉到列表底部再去請求數據而後添加到列表中,可是 Texture 提供了另一種更「合理」的方式,原文是這樣描述的:
By default, as a user is scrolling, when they approach the point in the table or collection where they are 2 「screens」 away from the end of the current content, the table will try to fetch more data.
當列表滾到到距離底部還有兩個屏幕高度請求新的數據,這個閾值是能夠調整的。一旦距離底部達到兩個屏幕的高度的時候,就會調用前面提到的方法。因此用起來大概是這樣的:
- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context{ [context beginBatchFetching]; [listApi startWithBlockSuccess:^(HQHomeListApi *request) { @strongify(self); NSArray *array = [request responseJSONObject]; [self.dataSourceArray addObjectsFromArray:array]; [self.tableNode insertSections:[NSIndexSet indexSetWithIndexesInRange:rang] withRowAnimation:UITableViewRowAnimationNone]; [self updateHavMore:array]; [context completeBatchFetching:YES]; } failure:NULL]; } - (BOOL)shouldBatchFetchForTableNode:(ASTableNode *)tableNode{ return self.haveMore; }
shouldBatchFetchForTableNode
用來控制是否須要獲取更多數據。這種方式優勢在於在網絡情況好的狀況下用戶都不會感覺到已經加載了其餘數據並顯示,缺點在於網絡情況很差的狀況下用於即便列表已經下拉到底部也沒有任何提示。