好。。开始作下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排版