好。。開始作下ComponentKit使用總結。。源碼沒有看,只看了一些概念以及API。本篇文章主要總結下使用心得以及ComponentKit的理念。一切的分析都基於使用層面上的。。大神請打臉或略過~css
本文面向有必定開發經驗的iOSer,以及喜歡折騰的iOSer...html
傳統MVC模式,數據(s)-控制器(s)-視圖(s)之間的雙向流所產生的大量狀態將致使:
1)代碼激增
2)BUG出現得機率增大
3)視圖渲染效率低
4)APP流暢度不高(特指ScrollView不能達到60FPS)
因此咱們須要一個更爲簡單粗暴的框架來寫咱們的APP。。(真正的緣由,毫不會告訴你其實只是被要求...)ios
既然名字叫作ComponentKit
,天然先說說Component(元素)
。對於開發者來講,全部的圖層(可見/不可見)其實都是由一個元素排版而來。不一樣的元素根據不一樣的排版展現出不一樣的視圖。我作個類比:正如中國四大發明之一-活字印刷同樣經過改變排版能夠展現不一樣的文字內容。
這裏引用文檔的一句話:css3
A simple analogy is to think of a component as a stencil: a fixed description that can be used to paint a view but that is not a view itself.app
意思也大概如此Component不併不直接當作視圖(印刷出來的東西)展現,而是告訴你視圖長什麼樣(印刷模具的存在)~框架
三大特性:(不明覺厲的地方)異步
stack
爲主(這裏我翻譯成「(縱向或者橫向)堆砌」)排版模具來告訴咱們某一個元素A的子元素在A中如何排列。我的使用的心得?:數據單向流,好處無非在於什麼樣的數據決定什麼樣的視圖,咱們能夠無視不少各類交互產生的狀態,而僅僅只須要把精力放在數據層上,寫好排版方程(functional)彷佛好像能夠作到一勞永逸。可是正由於如此,ComponentKit在寫動畫的時候註定較麻煩,由於數據變化是連續的~~也就是model是不斷變化的。使用上能夠作一些取捨。用ComponentKit的好處就在於寫代碼能夠處於無腦狀態,抓着繩子(數據)的一端就好,不容易打死結~ide
至於動畫方面的解釋,FB如是說:函數
Dynamic gesture-driven UIs are currently hard to implement in ComponentKit; consider using AsyncDisplayKit.佈局
CKComponent
上面說了Component是不可變的,且其能夠在任何線程進行建立,避免了出如今主線程的競爭。
這裏主要是兩個API:
/** Returns a new component. */ + (instancetype)newWithView:(const CKComponentViewConfiguration &)view size:(const CKComponentSize &)size; /** Returns a layout for the component and its children. */ - (CKComponentLayout)layoutThatFits:(CKSizeRange)constrainedSize parentSize:(CGSize)parentSize;
一個用來建立Component,一個用來進行排版。
Composite Components
這裏只有一句話重點:任何狀況自定義component下不要繼承CKComponent
,而是繼承Composite Components
。
大概緣由就是不要污染清純的父類Component,不要由於一個簡單得需求而直接進行繼承並重寫父類方法(不少弊端,FB blabla),而應該採用修飾的手段來達成(裝飾設計模式?)。
這裏給出壞代碼以及推薦代碼示例:
// 不推薦的壞代碼: @implementation HighlightedCardComponent : CardComponent - (UIColor *)backgroundColor { // This breaks silently if the superclass method is renamed. return [UIColor yellowColor]; } @end // 推薦代碼: @implementation HighlightedCardComponent : CKCompositeComponent + (instancetype)newWithArticle:(CKArticle *)article { return [super newWithComponent: [CardComponent newWithArticle:article backgroundColor:[UIColor yellowColor]]]; } @end
建立一個元素的類方法
+ (instancetype)newWithView:(const CKComponentViewConfiguration &)view size:(const CKComponentSize &)size;
這裏說下第一個參數告訴CK用什麼圖層類,第二個參數告訴CK如何配置這個圖層類。
舉個栗子
[CKComponent newWithView:{ [UIImageView class], { {@selector(setImage:), image}, {@selector(setContentMode:), @(UIViewContentModeCenter)} // Wrapping into an NSNumber } } size:{image.size.width, image.size.height}];
一樣能夠設置空值,舉個栗子:
[CKComponent newWithView:{} size:{}] // 更爲直接 [CKComponent new]
與UIView中的layoutSubViews
對應的是CK中的layoutThatFits:
。
這裏主要介紹幾個經常使用的Layout Components
CKStackLayoutComponent
橫向或者縱向堆砌子元素CKInsetComponent
內陷與大蘋果內陷類似CKBackgroundLayoutComponent
擴展底部的元素做爲背景CKOverlayLayoutComponent
擴展覆蓋層的元素做爲遮罩CKCenterLayoutComponent
在空間內居中排列CKRatioLayoutComponent
有比例關係的元素CKStaticLayoutComponent
可指定子元素偏移量FB中的響應者鏈與蘋果相似,可是二者是分離的。
FB中的鏈大概長相:
兒子component-> 兒子componentController(若是有) -> 父親component-> 父親componentController(若是有) -> (...遞歸 blabla) -> 【經過CKComponentActionSend
橋接】-> (過程:找到被附着的那個View,經過這個View找到最底層的葉子節點ViewA -> (往上遍歷ViewA的父親ViewB -> (...遞歸 blabla)。
這裏一個要點是component不是UIResponder子類,天然沒法成爲第一響應者~
解決發生在UIControl視圖上的點擊事件很簡單,只要將某個SEL綁定到CKComponentActionAttribute
便可,在接收外界UIControlEvent
時候觸發:
@implementation SomeComponent + (instancetype)new { return [self newWithView:{ [UIButton class], {CKComponentActionAttribute(@selector(didTapButton))} }]; } - (void)didTapButton { // Aha! The button has been tapped. } @end
以上對UIControl適用,通常View則要使用綁定一些更牛逼,更直接的屬性,好比tap手勢綁定SEL到CKComponentTapGestureAttribute
,代碼以下:
@implementation SomeComponent + (instancetype)new { return [self newWithView:{ [UIView class], {CKComponentTapGestureAttribute(@selector(didTapView))} }]; } - (void)didTapView { // The view has been tapped. } @end
一句話?元素Action機制 就是經過無腦綁定SEL,順着響應鏈找到能夠響應該SEL的元素。
FB也很淫蕩的作了廣告:
ComponentKit really shines when used with a UICollectionView.
吐槽下,之因此特意強調,那是必須啊,任何一款APP都特麼離不開UITableView或者UICollectionView。只要會UITableView或者UICollectionView那就具有了獨立開發的能力,精通這二者的就算具有了犀利哥的潛質。
FB鼓吹的優勢:
CKComponentDataSource
負責。PS:CKComponentDataSource
模塊的主要功能:
1)提供輸入數據源的操做指令以及數據
2)變化後的數據源佈局後臺生成
3)提供UITableView
或者UICollectionView
可用的輸出數據源
CKComponentCollectionViewDataSource
CKComponentCollectionViewDataSource
是CKComponentDataSource
的簡單封裝。
存在價值:
UICollectionView
適時進行添加/插入/更新「行」,「段」。UICollectionView
「行」和「段「排版信息。UICollectionView
可見行講同步調用cellForItemAtIndexPath:
這裏UICollectionView
與CKCollectionViewDataSource
數據表現來講還是單向的。
Component Provider
CKCollectionViewDataSource
負責將每個數據丟給元素(component)進行自我Config。在任什麼時候候須要有某一個元素(component)須要數據進行配置將會把CKCollectionViewDataSource
提供的數據源經過CKComponentProvider
提供的類方法傳入:
@interface MyController <CKComponentProvider> ... @end @implementation MyController ... + (CKComponent *)componentForModel:(MyModel*)model context:(MyContext*)context { return [MyComponent newWithModel:model context:context]; } ...
CKCollectionViewDataSource
帶入。它通常是:1)設備類型 2)外部依賴 好比圖片下載器CKCollectionViewDataSource
:- (void)viewDidLoad { ... self.dataSource = _dataSource = [[CKCollectionViewDataSource alloc] initWithCollectionView:self.collectionView supplementaryViewDataSource:nil componentProvider:[self class] context:context cellConfigurationFunction:nil];
須要作的就是將Model與indexPath進行綁定:
- (void)viewDidAppear { ... CKArrayControllerSections sections; CKArrayControllerInputItems items; // Don't forget the insertion of section 0 sections.insert(0); items.insert({0,0}, firstModel); // You can also use NSIndexPath NSIndexPath indexPath = [NSIndexPath indexPathForItem:1 inSection:0]; items.insert(indexPath, secondModel); [self.dataSource enqueueChangeset:{sections, items} constrainedSize:{{0,0}, {50, 50}}]; }
好比indexPath(0, 0),model是一個字符串「我是0段0行」,告訴CKCollectionViewDataSource
將他們綁在一塊兒。
由於無腦,只貼代碼:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { return [self.dataSource sizeForItemAtIndexPath:indexPath]; }
由於無腦,只貼代碼:
- (void)dataSource:(CKCollectionViewDataSource *)dataSource didSelectItemAtIndexPath:(NSIndexPath *)indexPath { MyModel *model = (MyModel *)[self.dataSource modelForItemAtIndexPath:indexPath]; NSURL *navURL = model.url; if (navURL) { [[UIApplication sharedApplication] openURL:navURL]; } }
這裏主要是指與數據源交互部分的API,主要分爲三類:
貼代碼:
CKArrayControllerInputItems items; // Insert an item at index 0 in section 0 and compute the component for the model @"Hello" items.insert({0, 0}, @"Hello"); // Update the item at index 1 in section 0 and update it with the component computed for the model @"World" items.update({0, 1}, @"World"); // Delete the item at index 2 in section 0, no need for a model here :) Items.delete({0, 2}); Sections sections; sections.insert(0); sections.insert(2); sections.insert(3); [datasource enqueueChangeset:{sections, items}];
這裏須要注意的是:
1)
The order in which commands are added to the changeset doesn't define the order in which those changes will eventually be applied to the
UICollectionView
(same forUITableViews
).
加入changeset的順序呢並不表明最終UICollectionView
最終應用上的改變順序。
2)
記得初始化的時候執行sections.insert(0);
3)
由於全部的改變集都是異步計算的,因此要當心數據與UI不一樣步的問題出現
3.1)
始終以datasource
爲惟一標準,不要試圖從曾經的數據源like下例中的_listOfModels獲取model:
@implementation MyAwesomeController { CKComponentCollectionViewDataSource *_datasource; NSMutableArray *_listOfModels; }
例子中的_datasource纔是正房,_listOfModels是小三。
堅持使用
[datasource objectAtindexPath:indexPath];
3.2)
不要執行像:Items.insert({0, _datasource.collectionView numberOfItemsInSection});
的語句,由於你所但願插入的位置未必是你想要插入的位置。
Facebook's iOS Infrastructure - @Scale 2014 - Mobile
OJBC.IO ComponentKit介紹
官方文檔
Making News Feed nearly 50% faster on iOS
Flexbox排版