简单介绍

IGListKit是Instagram推出的新的UICollectionView框架,使用数据驱动,旨在创造一个更快更灵活的列表控件。

github地址:https://github.com/Instagram/IGListKit

这个全新的控件一出来,我就赶快投入实践了一把。

先谈一谈我对这个控件的结论:这个框架设计的非常好,完美符合高内聚、低耦合。IGListKit 是一个很典型的使用 Objective-C 开发的,但却是个偏向使用 Swift 语言开发者的一个 UI 组件库。

使用过程也面临了一些疑惑,先谈一下使用收获:

  1. 它的优势在于flexible,比起原来的UICollectionView,在使用上更加灵活,在数据驱动上做的更好。

  2. 这个框架在fast上体现的还不够,但不妨碍我们自己进行下一步优化。

先看看IGListKit的结构

在原来的UICollectionViewController里的写法,我们一定都会实现UICollectionDataSource和UICollectionViewDelegate。

不过在IGListKit的实战过程中,你会发现似乎不用在ViewController中实现相关协议,取而代之的是SectionController来实现对应的方法:

class DemoSectionController: IGListSectionController, IGListSectionType{
var object: DemoItem?
func numberOfItems() -> Int {
return
}
func sizeForItem(at index: Int) -> CGSize {
return CGSize(width: collectionContext!.containerSize.width, height: )
}
func cellForItem(at index: Int) -> UICollectionViewCell {
let cell = collectionContext!.dequeueReusableCell(of: LabelCell.self, for: self, at: index) as! LabelCell
cell.label.text = object?.name
return cell
}
}

这里直接取了官方的Demo里的其中一个SectionController作为例子。其实UICollectionDataSource和UICollectionViewDelegate都交给了Adapter这个适配器中。我们来看一下IGAdapter.m文件中的源码:

当我们为适配器绑定collectionView时,调用如下方法

- (void)setCollectionView:(IGListCollectionView *)collectionView {
if (_collectionView != collectionView || _collectionView.dataSource != self) {
_collectionView = collectionView;
_collectionView.dataSource = self; [self updateCollectionViewDelegate];
[self updateAfterPublicSettingsChange];
}
}

其中self是指适配器对象。

接着适配器作为实现数据源协议的对象,我们来看一下它是怎么联系SectionController群的。

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
IGListSectionController *sectionController = [self.sectionMap sectionControllerForSection:indexPath.section];
_isDequeuingCell = YES;
UICollectionViewCell *cell = [sectionController cellForItemAtIndex:indexPath.item];
_isDequeuingCell = NO;
[self mapCell:cell toSectionController:sectionController]; return cell;
}

可以看到adapter通过遍历自己的sectionController的map来达到UICollectionView的数据源在cellForItem如何选择对应的sectionController。

坦白说,这样做,给人一种全新的思路,而且以后就算自己实现其实也并不复杂,可以参考其设计。

WorkRange能做的事

什么是WorkRange?还是用Github的官方介绍说的更快,更清楚。

大体就是说,我们可以指定左右的Working区间,干一些准备工作。

官网写的不多,只说了我们可以干事,具体干啥事,在我的个人实践中,我对它使用的理解是这样的。

更新数据源及预排版在ViewController进行,为Item设置layout属性。这样在SectionController中可以无需计算直接使用排版数据。

func updateItem(withItems items:Array) {
/*
假设我们在viewController中更新数据源,item为数据模型
Items = [CommentItem(name: "Mike", comment: ""),
CommentItem(name: "Chen", comment: ""),
....]
*/
commentGroup = CommentGroup(Items: Items) let queue = DispatchQueue(label: "myBackgroundQueue")
queue.async {
for item in Items {
let layout = CommentMainItemLayout(commentItem: item)
item.layout = layout
}
self.commentModels.append(self.commentGroup!) DispatchQueue.main.async { [weak self] in
self?.commentAdapter.performUpdates(animated: true, completion: nil)
}
}
}

而将预下载或者预渲染工作放在workRange中。

func listAdapter(_ listAdapter: IGListAdapter, sectionControllerWillEnterWorkingRange sectionController: IGListSectionController) {
for url: object.urls {
ImageCache.setImage(withUrl:url) //如果需要预渲染,可自行设定
}
} func listAdapter(_ listAdapter: IGListAdapter, sectionControllerDidExitWorkingRange sectionController: IGListSectionController) {
ImageCache.cancel()
}

Display Delegate

我还没来得及用到Display Delegate,但我觉得它非常适合在显示文本的控件上使用异步绘制

我们先来看一看它的调用顺序

  1. func cellForItem(at index: Int) -> UICollectionViewCell

  2. func listAdapterwillDisplay

  3. func listAdapterdidEndDisplaying

可以发现cellForItem在willDisplay前面,于是我会选择在cellForItem执行异步绘制。

在listAdapterdidEndDisplaying暂停异步绘制,最大程度上防止滑动速度过快,导致白白浪费去执行绘制任务。

和想象不一样的数据驱动

当初看到github中官方给的图是这样的:

我以为IGListKit里的数据驱动是类似双向绑定的结构,更新时不用手动显式的调用Update,可实际修改数据源模型,还是要显式调用

adapter.performUpdates(animated: true, completion: nil)

而这句代码对应的就是

/**
Perform an update from the previous state of the data source. This is analagous to calling
-[UICollectionView performBatchUpdates:completion:]. open func performUpdates(animated: Bool, completion: IGListKit.IGListUpdaterCompletion? = nil)

为什么称为Never Call呢?

再来看一下Diff算法

简单来说这个算法就是计算tableView或者collectionView前后数据变化增删改移关系的一个算法,时间复杂度是O(n),算是IGListKit的特色特点之一。

其实这个算法单独拿出来不只可以计算collectionView模型,稍加改造,也适用于其他模型或者文件的变化

使用的是Paul Heckel 的A technique for isolating differences between files 的算法,这份paper是收费。

不过这并不妨碍我们直接看源码,我们可以看一下IGListDiff.mm文件,该算法使用C++来编写。

主要是通过hashtable和新旧的两个数组结构:

用简单的例子来说,这里我模拟的是从假设原来的 1,2,4,1的旧数据模型到新的1,2,3,5的数据模型的变化过程,假想成Swift中代码,应该是这样的:

let oldModel = [
Num(id: , name: ""),
Num(id: , name: ""),
Num(id: , name: ""),
Num(id: , name: ""),
]
let newModel = [
Num(id: , name: ""),
Num(id: , name: ""),
Num(id: , name: ""),
Num(id: , name: ""),
]
let result = IGListDiffPaths(, , from, to, .equality).forBatchUpdates()
tableView.beginUpdates()
tableView.deleteRows(at: result.deletes, with: .fade)
tableView.insertRows(at: result.inserts, with: .fade)
for move in result.moves {
tableView.moveRow(at: move.from, to: move.to)
}
tableView.endUpdates()

首先oldIndexs是一个栈的结构,过程是先遍历新数组,将数组里模型的id对应的hash值作为key,找到对应的Num成员对象(实际代码中为entry,可以理解为一种抽象)的oldIndexs栈存入NSNotFound。

再遍历旧数组,拿例子来说,就是将数组里模型的id 对应的hash值作为key,找到对应的Num成员对象里的oldIndexs栈增加旧数组的下标值。

如果是新增加的,那么在hashtable中key对应的value存入的Num成员对象就是notfound。

这样算法如图使用的数据结构(已简化,实际稍复杂些),可以绑定新旧数组的成员的对应关系,包括成员间的移动增加删除修改关系,对于像TableView或者CollectionView非常适合不过。

Instagram/IGListKit 实践谈(UICollectionView框架)的更多相关文章

  1. [小北De编程手记] : Lesson 05 玩转 xUnit.Net 之 从Assert谈UT框架实践

    这一篇,本文会介绍一下基本的断言概念,但重点会放在企业级单元测试的相关功能上面.下面来跟大家分享一下xUnit.Net的断言,主要涉及到以下内容: 关于断言的概念 xUnit.Net常用的断言 关于单 ...

  2. ASP.NET MVC5 网站开发实践(一) - 项目框架

    前几天算是开题了,关于怎么做自己想了很多,但毕竟没做过项目既不知道这些想法有无必要,也不知道能不能实现,不过邓爷爷说过"摸着石头过河"吧.这段时间看了一些博主的文章收获很大,特别是 ...

  3. ASP.NET MVC5 网站开发实践(一) - 项目框架(转)

    前几天算是开题了,关于怎么做自己想了很多,但毕竟没做过项目既不知道这些想法有无必要,也不知道能不能实现,不过邓爷爷说过“摸着石头过河”吧.这段时间看了一些博主的文章收获很大,特别是@kencery,依 ...

  4. 【军哥谈CI框架】之入门教程之第二讲:分析CI结构和CI是怎么工作的

    [军哥谈CI框架]之入门教程之第二讲:分析CI结构和CI是怎么工作的   之入门教程之第二讲:分析CI结构和CI是如何工作的大家好!上一节,我们共同部署了一个CI网站,做到这一点非常简单,但是,亲们, ...

  5. 从实践谈iOS生命周期

    从实践谈iOS生命周期 个人感觉生命周期无论在Android,还是iOS都是很重要的概念,因为在每个声明周期的状态下我们可以做很多预加载或者处理的操作.因此在这里主要总结下ViewController ...

  6. 郑晔谈 Moco 框架的开发:写一个好的内部 DSL ,写一个表达性好的程序

    作者:张龙 出处:http://www.infoq.com/cn/news/2013/07/zhengye-on-moco 郑晔谈Moco框架的开发:写一个好的内部DSL,写一个表达性好的程序 作者  ...

  7. 王晶:华为云OCR文字识别服务技术实践、底层框架及应用场景 | AI ProCon 2019

    演讲嘉宾 | 王晶(华为云人工智能高级算法工程师王晶) 出品 | AI科技大本营(ID:rgznai100) 近期,由 CSDN 主办的 2019 中国AI 开发者大会(AI ProCon 2019) ...

  8. 手撸ORM浅谈ORM框架之基础篇

    好奇害死猫 一直觉得ORM框架好用.功能强大集众多优点于一身,当然ORM并非完美无缺,任何事物优缺点并存!我曾一度认为以为使用了ORM框架根本不需要关注Sql语句如何执行的,更不用关心优化的问题!!! ...

  9. 手撸ORM浅谈ORM框架之Add篇

    快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...

随机推荐

  1. Android教材 | 第三章 Android界面事件处理(一)—— 杰瑞教育原创教材试读

      前  言 JRedu Android应用开发中,除了界面编程外,另一个重要的内容就是组件的事件处理.在Android系统中,存在多种界面事件,比如触摸事件.按键事件.点击事件等.在用户交互过程中, ...

  2. python3 验证码图片切割

    切割前图片 切割后四个图片 代码 #coding:utf8 import os from PIL import Image,ImageDraw,ImageFile import numpy impor ...

  3. 在Android上山寨了一个Ios9的LivePhotos,放Github上了

    9月10号的凌晨上演了一场IT界的春晚,相信很多果粉(恩,如果你指坚果,那我也没办法了,是在下输了)都熬夜看了吧,看完打算去医院割肾了吧.在发布会上发布了游戏机 Apple TV,更大的砧板 Ipad ...

  4. 【Spark】Spark-foreachRDD需要注意的问题

    Spark-foreachRDD需要注意的问题 dstream.foreachRDD_百度搜索 通过Spark Streaming的foreachRDD把处理后的数据写入外部存储系统中 - 吾心光明 ...

  5. jquery事件使用方法总结 (转)

    http://www.cnblogs.com/cwp-bg/p/7668940.html jquery提供了许多的事件处理函数,学习前端一段时间了,下面对其总结一下,梳理一下知识点. 一.鼠标事件 1 ...

  6. Linux获取进程执行时间

    1.前言    测试一个程序的执行时间,时间包括用户CPU时间.系统CPU时间.时钟时间.之前获取之前时间都是在程序的main函数用time函数实现,这个只能粗略的计算程序的执行时间,不能准确的获取其 ...

  7. BCG在程序中的使用

    首先你电脑上是安装有BCG的,详细安装方法就是先双击安装程序,之后编译当中的两个project.之后将其生成的.dll\.lib文件放入C++的include中这样就能够使用BCG的控件了. 1. 在 ...

  8. javascript扩展时间方法,格式化,加减日期

    /** *对Date的扩展,将 Date 转化为指定格式的String *月(M).日(d).小时(h).分(m).秒(s).季度(q) 可以用 1-2 个占位符, *年(y)可以用 1-4 个占位符 ...

  9. 【Python】列表(数组)的引用和拷贝

    # Python里对象赋值传递的引用 arr=[1,2,3,4,5] newArr=arr arr[1]=9 print('arr='+str(arr)) print('newArr='+str(ne ...

  10. iOS 生成pem证书

    openssl pkcs12 -in Certificates.p12 -out Certificates.pem -nodes     需要通过终端命令将这些文件转换为PEM格式:openssl p ...