iOS:ComponentKit 使用总结

前言

传统MVC模式,数据(s)-控制器(s)-视图(s)之间的双向流所产生的大量状态将导致:
1)代码激增
2)BUG出现得概率增大
3)视图渲染效率低
4)APP流畅度不高(特指ScrollView不能达到60FPS)
所以我们需要一个更为简单粗暴的框架来写我们的APP。。(真正的原因,绝不会告诉你其实只是被要求...)

理念

既然名字叫做ComponentKit,自然先说说Component(元素)。对于开发者来说,所有的图层(可见/不可见)其实都是由一个元素排版而来。不同的元素根据不同的排版展示出不同的视图。我做个类比:正如中国四大发明之一-活字印刷一样通过改变排版可以展示不同的文字内容。
这里引用文档的一句话:

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.

意思也大概如此Component不并不直接当做视图(印刷出来的东西)展示,而是告诉你视图长什么样(印刷模具的存在)~

特性

三大特性:(不明觉厉的地方)

  • 描述性:通过stack为主(这里我翻译成“(纵向或者横向)堆砌”)排版模具来告诉我们某一个元素A的子元素在A中如何排列。
  • 函数式:保证数据流是单向的,也就是数据决定Component。比如方程“1 + X”,如果X=2或者X=3相对应结果“1 + 2”与“1 + 3”是固定的一样。数据如果确定了,那么结果就是不变的。当数据发生改变的时候,对应的component会进行重新渲染。(这里FB宣称该框架会尽量少的重新渲染,没有读过代码,没有发言权)
  • 可组合:这里可以想下积木,有些部分写成component,其他地方可以重用。

个人使用的心得?:数据单向流,好处无非在于什么样的数据决定什么样的视图,我们可以无视很多各种交互产生的状态,而仅仅只需要把精力放在数据层上,写好排版方程(functional)似乎好像可以做到一劳永逸。但是正因为如此,ComponentKit在写动画的时候注定较麻烦,因为数据变化是连续的~~也就是model是不断变化的。使用上可以做一些取舍。用ComponentKit的好处就在于写代码可以处于无脑状态,抓着绳子(数据)的一端就好,不容易打死结~

至于动画方面的解释,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排版

时间: 2024-10-29 19:52:45

iOS:ComponentKit 使用总结的相关文章

iOS 保持界面流畅的技巧

这篇文章会非常详细的分析 iOS 界面构建中的各种性能问题以及对应的解决思路,同时给出一个开源的微博列表实现,通过实际的代码展示如何构建流畅的交互. Index演示项目屏幕显示图像的原理卡顿产生的原因和解决方案CPU 资源消耗原因和解决方案GPU 资源消耗原因和解决方案AsyncDisplayKitASDK 的由来ASDK 的资料ASDK 的基本原理ASDK 的图层预合成ASDK 异步并发操作Runloop 任务分发微博 Demo 性能优化技巧预排版预渲染异步绘制全局并发控制更高效的异步图片加载

iOS开发 非常全的三方库、插件、大牛博客等等

UI 下拉刷新 EGOTableViewPullRefresh- 最早的下拉刷新控件. SVPullToRefresh- 下拉刷新控件. MJRefresh- 仅需一行代码就可以为UITableView或者CollectionView加上下拉刷新或者上拉刷新功能.可以自定义上下拉刷新的文字说明.具体使用看"使用方法". (国人写) XHRefreshControl- XHRefreshControl 是一款高扩展性.低耦合度的下拉刷新.上提加载更多的组件.(国人写) CBStoreHo

iOS中文版资源库,非常全

目录 入门 库和框架 音频 动画 Apple TV 桥接 缓存 Core Data 图表 数据库 硬件 动作 蓝牙 位置 iBeacon HUD 事件总线( EventBus ) 文件 JSON 布局 日志 地图 媒体 图片 视频 PDF 消息 网络 推送通知 Passbook 权限 文本 浏览 / 介绍 / 教程 URL Scheme UI Websocket 代码质量 分析 支付 产品化工具 实用工具 安全 安装项目 依赖 / 包管理 测试 测试驱动开发(TDD) / 行为驱动开发(BDD)

新浪微博iOS客户端架构与优化之路

随着Facebook.Twitter.微博的崛起,向UGC.PGC.OGC,自媒体提供平台的内 容消费型App逐渐形成了独特的客户端架构模式.与电商和通讯工具类App不同,微博客户端具有多信息流.内容丰富多样.对数据量和延迟敏感等特点.微博的信息流承载着文字.网页.照片.视频.直播等多样的内容形式,所以复杂信息流对团队的开发效率.App的性能都带来了极大的挑战. 2016年6月24-25日,GMTC全球移动技术大会将在北京举行.本届大会,我们邀请到了新浪微博移动端资深研发专家邱晨老师.曾就职于F

很好的iOS学习资料

https://github.com/vsouza/awesome-ios 汇集了很多好的资料 https://github.com/vsouza/awesome-ios Skip to content This repository Pull requests Issues Gist You don’t have any verified emails. We recommend verifying at least one email. Email verification helps ou

iOS开发之资料收集

github排名:https://github.com/trending, github搜索:https://github.com/search. 此文章转自github:https://github.com/Tim9Liu9/TimLiu-iOS UI 下拉刷新 EGOTableViewPullRefresh- 最早的下拉刷新控件. SVPullToRefresh- 下拉刷新控件. MJRefresh- 仅需一行代码就可以为UITableView或者CollectionView加上下拉刷新或者

iOS中文版资源库

我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列的资源整理.awesome-ios 就是 vsouza 发起维护的 iOS 资源列表,内容包括:框架.组件.测试.Apple Store.SDK.XCode.网站.书籍等.Swift 语言写成的项目会被标记为 ★ ,AppleWatch 的项目则会被标记为 ▲. Awesome 系列虽然挺全,但基本只对收录的资源做了极为简要的介绍,如果有更详细的中文介绍,对相应开发者的帮助会更大.这也是我们发起这个开源项目的初衷.

iOS 网络资源汇总

下拉刷新 模糊效果 AutoLayout 富文本 图表 表相关与Tabbar 隐藏与显示 HUD与Toast 对话框 其他UI 具体内容 下拉刷新 EGOTableViewPullRefresh - 最早的下拉刷新控件. SVPullToRefresh - 下拉刷新控件. MJRefresh - 仅需一行代码就可以为UITableView或者CollectionView加上下拉刷新或者上拉刷新功能.可以自定义上下拉刷新的文字说明.具体使用看"使用方法". (国人写) XHRefresh

GitHub上史上最全的iOS开源项目分类汇总

学了这么久,还是抽时间把github上比较好用的第三方总结了一下: Category/Util sstoolkit 一套Category类型的库,附带很多自定义控件 功能不错-       BFKit 又一套Category类型的 Kit,还有几个工具类       APUtils 又一套Category类型的 Kit       QSKit 又一套Category类型的 Kit       iOS-Categories 又一套Category类型的 Kit       BlocksKit 将B