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

相关文章
相关标签/搜索