iOS:ComponentKit 使用總結

前言的前言

好。。開始作下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中如何排列。
  • 函數式:保證數據流是單向的,也就是數據決定Component。好比方程「1 + X」,若是X=2或者X=3相對應結果「1 + 2」與「1 + 3」是固定的同樣。數據若是肯定了,那麼結果就是不變的。當數據發生改變的時候,對應的component會進行從新渲染。(這裏FB宣稱該框架會盡可能少的從新渲染,沒有讀過代碼,沒有發言權)
  • 可組合:這裏能夠想下積木,有些部分寫成component,其餘地方能夠重用。

我的使用的心得?:數據單向流,好處無非在於什麼樣的數據決定什麼樣的視圖,咱們能夠無視不少各類交互產生的狀態,而僅僅只須要把精力放在數據層上,寫好排版方程(functional)彷佛好像能夠作到一勞永逸。可是正由於如此,ComponentKit在寫動畫的時候註定較麻煩,由於數據變化是連續的~~也就是model是不斷變化的。使用上能夠作一些取捨。用ComponentKit的好處就在於寫代碼能夠處於無腦狀態,抓着繩子(數據)的一端就好,不容易打死結~ide

至於動畫方面的解釋,FB如是說:函數

Dynamic gesture-driven UIs are currently hard to implement in ComponentKit; consider using AsyncDisplayKit.佈局

API (官方文檔內容)

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

Views

建立一個元素的類方法

+ (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]

Layout && Layout Components

與UIView中的layoutSubViews對應的是CK中的
layoutThatFits:

這裏主要介紹幾個經常使用的Layout Components

  • CKStackLayoutComponent 橫向或者縱向堆砌子元素
  • CKInsetComponent內陷與大蘋果內陷類似
  • CKBackgroundLayoutComponent 擴展底部的元素做爲背景
  • CKOverlayLayoutComponent 擴展覆蓋層的元素做爲遮罩
  • CKCenterLayoutComponent 在空間內居中排列
  • CKRatioLayoutComponent 有比例關係的元素
  • CKStaticLayoutComponent 可指定子元素偏移量

響應者鏈 && Tap事件 && 手勢支持

響應者鏈

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

Component Actions

一句話?元素Action機制 就是經過無腦綁定SEL,順着響應鏈找到能夠響應該SEL的元素。

State (TO DO)

對iOS中容器類視圖的支持(UITableView, UICollectionView)

概述

FB也很淫蕩的作了廣告:

ComponentKit really shines when used with a UICollectionView.

吐槽下,之因此特意強調,那是必須啊,任何一款APP都特麼離不開UITableView或者UICollectionView。只要會UITableView或者UICollectionView那就具有了獨立開發的能力,精通這二者的就算具有了犀利哥的潛質。

FB鼓吹的優勢:

  1. 自動重用
  2. 流暢的滑動體驗 -> CK自身保證非UI相關的計算全在次線程
  3. 數據源 這個模塊由CKComponentDataSource負責。

PS:CKComponentDataSource模塊的主要功能:
1)提供輸入數據源的操做指令以及數據
2)變化後的數據源佈局後臺生成
3)提供UITableView或者UICollectionView可用的輸出數據源

CKComponentCollectionViewDataSource

CKComponentCollectionViewDataSourceCKComponentDataSource的簡單封裝。
存在價值:

  1. 負責讓UICollectionView適時進行添加/插入/更新「行」,「段」。
  2. 負責提供給UICollectionView「行」和「段「排版信息。
  3. UICollectionView可見行講同步調用cellForItemAtIndexPath:
  4. 保證返回配置好的cell

這裏UICollectionViewCKCollectionViewDataSource數據表現來講還是單向的。

基礎

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];
    }
    ...
  • 用類方法不用block 爲了保證數據是不可變的
  • 上下文 這裏能夠是任意不能夠變對象,其被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

這裏主要是指與數據源交互部分的API,主要分爲三類:

  1. 動做(針對行的插入/刪除/更新,針對段的插入/刪除)
  2. 位置指定(行/段位置指定)
  3. 分配數據(丟給Component用的)

貼代碼:

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 for UITableViews).

加入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排版

相關文章
相關標籤/搜索