开源信息

IGKListKit 是一个数据驱动的用于快速创建列表的框架,它有如下特点:

  • 🙅 Never call performBatchUpdates(_:, completion:) or reloadData() again
  • 🏠 Better architecture with reusable cells and components
  • 🔠 Create collections with multiple data types
  • 🔑 Decoupled diffing algorithm
  • ✅ Fully unit tested
  • 🔍 Customize your diffing behavior for your models
  • 📱 Simply UICollectionView at its core
  • 🚀 Extendable API
  • 🐦 Written in Objective-C with full Swift interop support
源码解析

整个IGKListKit代码还是蛮多的,我们在进行源码解析之前先对这些代码进行整理下,看下整个IGKListKit是由哪些部分构成的:

这里大致将整个代码分成两大部分:

  1. IGListDiffKit 里面存放的是IGListKit的diff 算法相关类
  2. IGListKit 里面存放的是整个框架代码,它是由如下几部分构成:
  • Adapter: 这是最核心的部分,是它将CollectionView,SessionController,Updater,Context关联起来。它从SessionController获取每个session的数据,通过Updater将数据加载到CollectionView。
  • Updater: 负责将数据加载到CollectionView
  • CollectionView: 负责数据的展示
  • Context: 一些环境上下文全局内容
  • Debug: 用于辅助调试的内容

这篇博客会从数据源开始,从数据源的提供,根据数据源选择SessionController,再通过Updater将数据加载到UICollectionView.

数据源

IGListKit 数据源都必须遵循IGListAdapterDataSource协议:

/**
根据传入的IGListAdapter来返回要展示的数据源,注意这里的数组中的每个元素对应的是一个session中要展示的数据集,返回的是多个session数据集的集合。
*/
- (NSArray<id <IGListDiffable>> *)objectsForListAdapter:(IGListAdapter *)listAdapter;

/**
传入特定的对象,也就是一个session的数据集合,返回它对应的sessionController,我们可以在这里建立sessionController与对象数据集合的对应关系。这里也是新建sessionController的地方,我们可以在这里传数据给sessionController
*/
- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object;

/**
当列表的数据集合为空的时候可以通过这个位置返回空页面需要展示的内容
*/
- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter;

IGListAdapterDataSource中我们可以提供数据,空数据时候所要展示的界面,以及session数据与sessionController之间的映射关系。

IGListAdapter的初始化工作

IGListAdapter是整个IGListKit的核心部分,它负责调度各个类来完成从数据源获取数据,数据与sessionController的映射关系,使用Updater将数据加载到UICollectionView,分发各个代理事件等等功能,所以我们先来看下它是由哪些重要的组件构成,以及这些组件都有哪些功能:

/**
容纳适配器的视图控制器。
*/
@property (nonatomic, nullable, weak) UIViewController *viewController;
/**
适配器使用的UICollectionView
*/
@property (nonatomic, nullable, weak) UICollectionView *collectionView;
/**
IGListAdapter的数据源
*/
@property (nonatomic, nullable, weak) id <IGListAdapterDataSource> dataSource;
/**
某些对象将要显示或者已经显示完毕后的通知
*/
@property (nonatomic, nullable, weak) id <IGListAdapterDelegate> delegate;
/**
接受UICollectionView 代理事件的delegate
*/
@property (nonatomic, nullable, weak) id <UICollectionViewDelegate> collectionViewDelegate;
/**
接受UIScrollView代理事件的delegate
*/
@property (nonatomic, nullable, weak) id <UIScrollViewDelegate> scrollViewDelegate;
/**
重排session代理事件的delegate
*/
@property (nonatomic, nullable, weak) id <IGListAdapterMoveDelegate> moveDelegate NS_AVAILABLE_IOS(9_0);
/**
用于性能检测点的的delegate
*/
@property (nonatomic, nullable, weak) id <IGListAdapterPerformanceDelegate> performanceDelegate;
/**
Adapter的更新器
*/
@property (nonatomic, strong, readonly) id <IGListUpdatingDelegate> updater;
/**
对象与SessionController的映射关系
*/
@property (nonatomic, strong, readonly) IGListSectionMap *sectionMap;
/**
显示cell的管理
*/
@property (nonatomic, strong, readonly) IGListDisplayHandler *displayHandler;
/**
用于可见范围的管理
*/
@property (nonatomic, strong, readonly) IGListWorkingRangeHandler *workingRangeHandler;
/**
负责代理事件的分发
*/
@property (nonatomic, strong, nullable) IGListAdapterProxy *delegateProxy;
/**
空视图界面
*/
@property (nonatomic, strong, nullable) UIView *emptyBackgroundView;

/**
用于注册Cell
*/
@property (nonatomic, strong) NSMutableSet<NSString *> *registeredCellIdentifiers;
@property (nonatomic, strong) NSMutableSet<NSString *> *registeredNibNames;
@property (nonatomic, strong) NSMutableSet<NSString *> *registeredSupplementaryViewIdentifiers;
@property (nonatomic, strong) NSMutableSet<NSString *> *registeredSupplementaryViewNibNames;

上面列出了比较重要的组件,但是从整体功能上看最主要的组件包括:

UICollectionView:UICollectionView是整个列表的界面承载实体
IGListAdapterDataSource:IGListAdapterDataSource为整个IGListAdapter提供数据对象,并且指定每个session对应的sessionController,以及空数据时候的界面
IGListUpdatingDelegate:用于将数据加载到UICollectionView
****IGListAdapterDelegate/UICollectionViewDelegate/UIScrollViewDelegate/****:提供事件处理代理方法
IGListAdapterProxy:负责事件分发
IGListSectionMap:维护数据集与sessionController的映射关系
IGListDisplayHandler:负责可见对象的控制,这些可见对象存储于visibleViewObjectMap
IGListWorkingRangeHandler:负责工作区的管理
IGListAdapterMoveDelegate/IGListAdapterPerformanceDelegate:移动sessionController以及性能检测埋点的代理

我们看下 IGListAdapter 的初始化阶段:

- (instancetype)initWithUpdater:(id <IGListUpdatingDelegate>)updater
viewController:(UIViewController *)viewController
workingRangeSize:(NSInteger)workingRangeSize {

//........
if (self = [super init]) {
//初始化重要组件
NSPointerFunctions *keyFunctions = [updater objectLookupPointerFunctions];
NSPointerFunctions *valueFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory];
NSMapTable *table = [[NSMapTable alloc] initWithKeyPointerFunctions:keyFunctions valuePointerFunctions:valueFunctions capacity:0];
_sectionMap = [[IGListSectionMap alloc] initWithMapTable:table];
_displayHandler = [IGListDisplayHandler new];
_workingRangeHandler = [[IGListWorkingRangeHandler alloc] initWithWorkingRangeSize:workingRangeSize];
_updateListeners = [NSHashTable weakObjectsHashTable];
_viewSectionControllerMap = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality | NSMapTableStrongMemory
valueOptions:NSMapTableStrongMemory];
_updater = updater;
_viewController = viewController;
[IGListDebugger trackAdapter:self];
}
return self;
}

初始化任务中完成了大部分重要组件的初始化,上面已经介绍了这些组件的功能,这里就不重复介绍了。

- (void)setCollectionView:(UICollectionView *)collectionView {
// 如果collectionView 被用于另一个adapter,那么我们需要对它进行重置,断开与之前adapter之间的关联,避免会被之前对adapter错误更新。
if (_collectionView != collectionView || _collectionView.dataSource != self) {
//每个UICollectionView 都对应一个 IGListAdapter,globalCollectionViewAdapterMap 里面存放的是之间的对应关系
static NSMapTable<UICollectionView *, IGListAdapter *> *globalCollectionViewAdapterMap = nil;
if (globalCollectionViewAdapterMap == nil) {
globalCollectionViewAdapterMap = [NSMapTable weakToWeakObjectsMapTable];
}
[globalCollectionViewAdapterMap removeObjectForKey:_collectionView];
[[globalCollectionViewAdapterMap objectForKey:collectionView] setCollectionView:nil];
[globalCollectionViewAdapterMap setObject:self forKey:collectionView];
//globalCollectionViewAdapterMap ---> key:UICollectionView value:IGListAdapter

//注册cell之类的名字
_registeredCellIdentifiers = [NSMutableSet new];
_registeredNibNames = [NSMutableSet new];
_registeredSupplementaryViewIdentifiers = [NSMutableSet new];
_registeredSupplementaryViewNibNames = [NSMutableSet new];

const BOOL settingFirstCollectionView = _collectionView == nil;

//持有collectionView,并将adapter作为它的数据源
_collectionView = collectionView;
_collectionView.dataSource = self;

//.....
//将adapter保存到collectionViewLayout
[_collectionView.collectionViewLayout ig_hijackLayoutInteractiveReorderingMethodForAdapter:self];
[_collectionView.collectionViewLayout invalidateLayout];

//CollectionViewDelegate 要么设置为 delegateProxy 要么设置为adapter
[self _updateCollectionViewDelegate];

// only construct
if (!IGListExperimentEnabled(self.experiments, IGListExperimentGetCollectionViewAtUpdate)
|| settingFirstCollectionView) {
[self _updateAfterPublicSettingsChange];
}
}
}

- (void)_updateCollectionViewDelegate {
_collectionView.delegate = (id<UICollectionViewDelegate>)self.delegateProxy ?: self;
}

为IGListAdapter设置collectionView的时候要注意如果globalCollectionViewAdapterMap中已经存在了一个映射关系,需要重新移除后添加,之后为collectionView重新设置当前的Adapter作为新的dataSource,以及delegateProxy的设置,对于第一次创建的时候还需要调用_updateAfterPublicSettingsChange从datasource中取出属于当前adapter的数据,进行相关的更新。这部分在接下来的设置数据源也会涉及,我们就来看下数据源的设置:

IGListAdapter数据源的设置

- (void)setDataSource:(id<IGListAdapterDataSource>)dataSource {
if (_dataSource != dataSource) {
_dataSource = dataSource;
//数据源改变的时候会剔除重复数据后更新数据
[self _updateAfterPublicSettingsChange];
}
}

- (void)_updateAfterPublicSettingsChange {
id<IGListAdapterDataSource> dataSource = _dataSource;
if (_collectionView != nil && dataSource != nil) {
//去除当前Adapter中重复的元素
NSArray *uniqueObjects = objectsWithDuplicateIdentifiersRemoved([dataSource objectsForListAdapter:self]);
//更新数据源
[self _updateObjects:uniqueObjects dataSource:dataSource];
}
}

这里会通过[dataSource objectsForListAdapter:self],取出属于该adapter的数据对象。然后通过objectsWithDuplicateIdentifiersRemoved剔除掉重复的数据后,通过_updateObjects更新adapter中的其他模块数据,我们重点看下这个部分:

- (void)_updateObjects:(NSArray *)objects dataSource:(id<IGListAdapterDataSource>)dataSource {

//.......
NSMutableArray<IGListSectionController *> *sectionControllers = [NSMutableArray new];
NSMutableArray *validObjects = [NSMutableArray new];

IGListSectionMap *map = self.sectionMap;

// collect items that have changed since the last update
NSMutableSet *updatedObjects = [NSMutableSet new];

//遍历从数据源获取到的每个数据,注意这里的object其实是一个数据集,每个数据集针对一个sessionController
for (id object in objects) {
// 查看是否之前已经创建了sectionController,如果有就直接使用
IGListSectionController *sectionController = [map sectionControllerForObject:object];

// 如果之前没有我们就询问dataSource返回一个指定数据集对应的sessionController
if (sectionController == nil) {
sectionController = [dataSource listAdapter:self sectionControllerForObject:object];
}

//如果再没有直接报错
if (sectionController == nil) {
IGLKLog(@"WARNING: Ignoring nil section controller returned by data source %@ for object %@.",
dataSource, object);
continue;
}

// 将一些环境变量赋给sectionController,比如将当前的adapter作为collectionContext赋给sectionController
sectionController.collectionContext = self;
sectionController.viewController = self.viewController;

// 检查当前的对象是全新的还是对已经有的对象进行更新
const NSInteger oldSection = [map sectionForObject:object];
if (oldSection == NSNotFound || [map objectForSection:oldSection] != object) {
//更新的对象
[updatedObjects addObject:object];
}

//将结果记录到sectionControllers 与 validObjects
[sectionControllers addObject:sectionController];
[validObjects addObject:object];
}

// clear the view controller and collection context
IGListSectionControllerPopThread();

//更新到IGListSectionMap IGListSectionMap用于维护object 与sessionController的之间的关系
[map updateWithObjects:validObjects sectionControllers:sectionControllers];

// object 与 sessionController之间的关系已经建立,这时候可以认为sessionController已经加载完毕,加载完成后sessionController会收到didUpdateToObject的通知
for (id object in updatedObjects) {
[[map sectionControllerForObject:object] didUpdateToObject:object];
}

//计算总数量
NSInteger itemCount = 0;
for (IGListSectionController *sectionController in sectionControllers) {
itemCount += [sectionController numberOfItems];
}

//根据总数量决定显示空界面还是正常的UICollectionView
[self _updateBackgroundViewShouldHide:itemCount > 0];
}

- (void)_updateBackgroundViewShouldHide:(BOOL)shouldHide {

//如果还在更新那么先返回,在update block执行结束之后还会调用
if (self.isInUpdateBlock) {
return;
}
//根据itemCount数量决定是否显示空白页面
UIView *backgroundView = [self.dataSource emptyViewForListAdapter:self];
// don't do anything if the client is using the same view
if (backgroundView != _collectionView.backgroundView) {
// collection view will just stack the background views underneath each other if we do not remove the previous
// one first. also fine if it is nil
[_collectionView.backgroundView removeFromSuperview];
_collectionView.backgroundView = backgroundView;
}
_collectionView.backgroundView.hidden = shouldHide;
}

我们上面传入_updateObjects的objects是整个adapter所对应的数据集的数组,那么在_updateObjects中首先需要遍历数组中的每个数据集,我们知道object与sessionController的对应关系存在于两个地方IGListSectionMap以及IGListAdapter。首先我们会从IGListSectionMap中去检查是否有缓存了这种关系,如果没有再从IGListAdapter去获取。拿到之后再更新到IGListSectionMap以供下次使用。对于数据有变动的情况还会调用sessionController的didUpdateToObject方法。最后计算整个adapter的数据总数,如果数据总数为0那么会调用emptyViewForListAdapter获取空界面进行展示。

IGListAdapter代理设置

- (void)setCollectionViewDelegate:(id<UICollectionViewDelegate>)collectionViewDelegate {
//....
//_collectionViewDelegate 这个会传递到IGListAdapterProxy
if (_collectionViewDelegate != collectionViewDelegate) {
_collectionViewDelegate = collectionViewDelegate;
//创建IGListAdapterProxy并设置为delegate
[self _createProxyAndUpdateCollectionViewDelegate];
}
}

- (void)setScrollViewDelegate:(id<UIScrollViewDelegate>)scrollViewDelegate {
if (_scrollViewDelegate != scrollViewDelegate) {
_scrollViewDelegate = scrollViewDelegate;
//_scrollViewDelegate 这个会传递到IGListAdapterProxy
[self _createProxyAndUpdateCollectionViewDelegate];
}
}

- (void)_createProxyAndUpdateCollectionViewDelegate {
_collectionView.delegate = nil;
self.delegateProxy = [[IGListAdapterProxy alloc] initWithCollectionViewTarget:_collectionViewDelegate
scrollViewTarget:_scrollViewDelegate
interceptor:self];
[self _updateCollectionViewDelegate];
}

从上面代码看IGListAdapterProxy接受三个对象_collectionViewDelegate,_scrollViewDelegate以及adapter。然后再将IGListAdapterProxy作为collectionView的delegate。这样做的好处是可以通过IGListAdapterProxy进行统一管理,IGListAdapterProxy负责这些代理事件的集中分发。

- (BOOL)respondsToSelector:(SEL)aSelector {
return isInterceptedSelector(aSelector)
|| [_collectionViewTarget respondsToSelector:aSelector]
|| [_scrollViewTarget respondsToSelector:aSelector];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
if (isInterceptedSelector(aSelector)) {
return _interceptor;
}
return [_scrollViewTarget respondsToSelector:aSelector] ? _scrollViewTarget : _collectionViewTarget;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
void *nullPointer = NULL;
[invocation setReturnValue:&nullPointer];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}

在IGListAdapterProxy中会对这些代理方法进行拦截,如果在拦截清单内的话会将这些代理方法转到_interceptor也就是adapter,其他的则会根据_scrollViewTarget以及_collectionViewTarget是否有响应的方法来确定转发给谁,下面是拦截的方法:

static BOOL isInterceptedSelector(SEL sel) {
return (
// UIScrollViewDelegate
sel == @selector(scrollViewDidScroll:) ||
sel == @selector(scrollViewWillBeginDragging:) ||
sel == @selector(scrollViewDidEndDragging:willDecelerate:) ||
sel == @selector(scrollViewDidEndDecelerating:) ||
// UICollectionViewDelegate
sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) ||
sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) ||
sel == @selector(collectionView:didSelectItemAtIndexPath:) ||
sel == @selector(collectionView:didDeselectItemAtIndexPath:) ||
sel == @selector(collectionView:didHighlightItemAtIndexPath:) ||
sel == @selector(collectionView:didUnhighlightItemAtIndexPath:) ||
// UICollectionViewDelegateFlowLayout
sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
sel == @selector(collectionView:layout:insetForSectionAtIndex:) ||
sel == @selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:) ||
sel == @selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:) ||
sel == @selector(collectionView:layout:referenceSizeForFooterInSection:) ||
sel == @selector(collectionView:layout:referenceSizeForHeaderInSection:) ||

// IGListCollectionViewDelegateLayout
sel == @selector(collectionView:layout:customizedInitialLayoutAttributes:atIndexPath:) ||
sel == @selector(collectionView:layout:customizedFinalLayoutAttributes:atIndexPath:)
);
}

Object && sessionController 之间的映射关系

目前在IGListKit可以完成如下几种对象之间的映射关系:

* indexPath <--indexPath.section--> section <---sectionControllerForSection/sectionForSectionController---> IGListSectionController <--sectionControllerForObject/objectForSection-->object

* indexPath --sectionControllerForSection--supplementaryViewSource--> IGListSupplementaryViewSource

这部分代码就不贴出来了,最底层逻辑在sectionMap上,大家可以查看对应的源码。

可见性通知

这部分主要涉及IGListDisplayDelegateIGListAdapterDelegateIGListDisplayHandler这三个类,我们可以指定IGListAdapter的delegate来监听可见性情况:

@protocol IGListAdapterDelegate <NSObject>
/**
通知delegate某个数据集即将显示
*/
- (void)listAdapter:(IGListAdapter *)listAdapter willDisplayObject:(id)object atIndex:(NSInteger)index;

/**
通知delegate某个数据集合将不在显示范围内
*/
- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingObject:(id)object atIndex:(NSInteger)index;
@end

IGListAdapterDelegate其实和IGListDisplayDelegate是一样的,只不过一个是在adapter上层监听,一个是在sessionController监听。IGListDisplayDelegate其实监听的是更细粒度的可以通过代理知道具体哪个cell进入可见区域,我们在看下IGListDisplayDelegate:

@protocol IGListDisplayDelegate <NSObject>

/**
指定的sessionController进入到可见区域
*/
- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController;

/**
指定的sessionController离开可见区域
*/
- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController;

/**
在某个sessionController的某个cell进入到可见区域
*/
- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController
cell:(UICollectionViewCell *)cell
atIndex:(NSInteger)index;

/**
某个sessionController的某个cell离开可见区域
*/
- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController
cell:(UICollectionViewCell *)cell
atIndex:(NSInteger)index;

@end

IGListAdapterDelegate和IGListDisplayDelegate对应的方法是在IGListDisplayHandler中被调用的。IGListDisplayHandler中维护着一份visibleListSections以及visibleViewObjectMap;visibleListSections里面存储着当前可见的sessionController,visibleViewObjectMap里面存储的是数据对象以及CellView的映射关系。在sessionController进入离开可见区域的时候都会维护这两个对象里面的内容,并且通知adapter以及sessionController中的对应delegate。

- (void)_willDisplayReusableView:(UICollectionReusableView *)view
forListAdapter:(IGListAdapter *)listAdapter
sectionController:(IGListSectionController *)sectionController
object:(id)object
indexPath:(NSIndexPath *)indexPath {
//....
//self.visibleViewObjectMap 中存储的是数据对象以及CellView
[self.visibleViewObjectMap setObject:object forKey:view];
//可见的session部分
NSCountedSet *visibleListSections = self.visibleListSections;
if ([visibleListSections countForObject:sectionController] == 0) {
/*如果当前的sectionController没有在可见session列表中则触发通知给sectionController以及listAdapter*/
[sectionController.displayDelegate listAdapter:listAdapter willDisplaySectionController:sectionController];
[listAdapter.delegate listAdapter:listAdapter willDisplayObject:object atIndex:indexPath.section];
}
//将当前的sectionController添加到visibleListSections 避免重复通知
[visibleListSections addObject:sectionController];
}

- (void)_didEndDisplayingReusableView:(UICollectionReusableView *)view
forListAdapter:(IGListAdapter *)listAdapter
sectionController:(IGListSectionController *)sectionController
object:(id)object
indexPath:(NSIndexPath *)indexPath {
IGParameterAssert(view != nil);
IGParameterAssert(listAdapter != nil);
IGParameterAssert(indexPath != nil);

if (object == nil || sectionController == nil) {
return;
}

const NSInteger section = indexPath.section;
//移除sectionController并通知sectionController以及listAdapter
NSCountedSet *visibleSections = self.visibleListSections;
[visibleSections removeObject:sectionController];

if ([visibleSections countForObject:sectionController] == 0) {
[sectionController.displayDelegate listAdapter:listAdapter didEndDisplayingSectionController:sectionController];
[listAdapter.delegate listAdapter:listAdapter didEndDisplayingObject:object atIndex:section];
}
}

那么IGListDisplayHandler这些方法是谁触发的呢?嗯,是Adapter触发的,我们上面介绍IGListAdapterProxy的时候讲到Adapter会拦截部分的代理方法,在这部分代理方法中就可以调用IGListDisplayHandler发出通知。这部分代码在IGListAdapter+UICollectionView,这里面包括了可见区域的通知,以及工作区相关的处理。由于同时需要注意的是由于在IGListAdapterProxy会对这部分代理方法进行拦截,所以这里还需要将事件通知到collectionViewDelegate

- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
id<IGListAdapterPerformanceDelegate> performanceDelegate = self.performanceDelegate;
[performanceDelegate listAdapterWillCallDisplayCell:self];

// 由于在IGListAdapterProxy会对这部分请求进行拦截,所以这里还需要将事件通知到collectionViewDelegate
id<UICollectionViewDelegate> collectionViewDelegate = self.collectionViewDelegate;
if ([collectionViewDelegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)]) {
[collectionViewDelegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
}

// 获取到sessionController
IGListSectionController *sectionController = [self sectionControllerForView:cell];
// if the section controller relationship was destroyed, reconnect it
// this happens with iOS 10 UICollectionView display range changes
if (sectionController == nil) {
sectionController = [self sectionControllerForSection:indexPath.section];
//这里为了建立cell与sessionController之间的关系,供sectionControllerForView使用
[self mapView:cell toSectionController:sectionController];
}

//取出当前sessionController对应的数据集
id object = [self.sectionMap objectForSection:indexPath.section];
//通知对应的delegate
[self.displayHandler willDisplayCell:cell forListAdapter:self sectionController:sectionController object:object indexPath:indexPath];

//工作区相关更新
_isSendingWorkingRangeDisplayUpdates = YES;
[self.workingRangeHandler willDisplayItemAtIndexPath:indexPath forListAdapter:self];
_isSendingWorkingRangeDisplayUpdates = NO;

[performanceDelegate listAdapter:self didCallDisplayCell:cell onSectionController:sectionController atIndex:indexPath.item];
}

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
id<IGListAdapterPerformanceDelegate> performanceDelegate = self.performanceDelegate;
[performanceDelegate listAdapterWillCallEndDisplayCell:self];

// 由于在IGListAdapterProxy会对这部分请求进行拦截,所以这里还需要将事件通知到collectionViewDelegate
id<UICollectionViewDelegate> collectionViewDelegate = self.collectionViewDelegate;
if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)]) {
[collectionViewDelegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath];
}

// 获取到sessionController
IGListSectionController *sectionController = [self sectionControllerForView:cell];
//通知对应的delegate
[self.displayHandler didEndDisplayingCell:cell forListAdapter:self sectionController:sectionController indexPath:indexPath];
//工作区相关更新
[self.workingRangeHandler didEndDisplayingItemAtIndexPath:indexPath forListAdapter:self];

// break the association between the cell and the section controller
[self removeMapForView:cell];

[performanceDelegate listAdapter:self didCallEndDisplayCell:cell onSectionController:sectionController atIndex:indexPath.item];
}

- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath {

// 由于在IGListAdapterProxy会对这部分请求进行拦截,所以这里还需要将事件通知到collectionViewDelegate
id<UICollectionViewDelegate> collectionViewDelegate = self.collectionViewDelegate;
if ([collectionViewDelegate respondsToSelector:@selector(collectionView:willDisplaySupplementaryView:forElementKind:atIndexPath:)]) {
[collectionViewDelegate collectionView:collectionView willDisplaySupplementaryView:view forElementKind:elementKind atIndexPath:indexPath];
}

IGListSectionController *sectionController = [self sectionControllerForView:view];
// if the section controller relationship was destroyed, reconnect it
// this happens with iOS 10 UICollectionView display range changes
if (sectionController == nil) {
sectionController = [self.sectionMap sectionControllerForSection:indexPath.section];
[self mapView:view toSectionController:sectionController];
}

id object = [self.sectionMap objectForSection:indexPath.section];
//通知对应的delegate
[self.displayHandler willDisplaySupplementaryView:view forListAdapter:self sectionController:sectionController object:object indexPath:indexPath];
}

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath {
// 由于在IGListAdapterProxy会对这部分请求进行拦截,所以这里还需要将事件通知到collectionViewDelegate
id<UICollectionViewDelegate> collectionViewDelegate = self.collectionViewDelegate;
if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didEndDisplayingSupplementaryView:forElementOfKind:atIndexPath:)]) {
[collectionViewDelegate collectionView:collectionView didEndDisplayingSupplementaryView:view forElementOfKind:elementKind atIndexPath:indexPath];
}

IGListSectionController *sectionController = [self sectionControllerForView:view];
//通知对应的delegate
[self.displayHandler didEndDisplayingSupplementaryView:view forListAdapter:self sectionController:sectionController indexPath:indexPath];

[self removeMapForView:view];
}

工作区管理

对于工作区管理会在进入显示区的时候将当前的indexPath 信息添加到_visibleSectionIndices,退出可视区域的时候从_visibleSectionIndices中删除。然后根据workingRangeSize来确定起始和结束的index,根据确定的起始index和结束index将可见的sessionController添加到workingRangeSectionControllers,再通过新的workingRangeSectionControllers与旧的workingRangeSectionControllers进行对比,如果只出现再新的,而在老的没出现,这时候表示当前的sessionController为新进入工作区的,反之则为退出工作区的。

- (void)willDisplayItemAtIndexPath:(NSIndexPath *)indexPath
forListAdapter:(IGListAdapter *)listAdapter {

// 往_visibleSectionIndices中插入可见的NSIndexPath信息
_visibleSectionIndices.insert({
.section = indexPath.section,
.row = indexPath.row,
.hash = indexPath.hash
});
[self _updateWorkingRangesWithListAdapter:listAdapter];
}

- (void)didEndDisplayingItemAtIndexPath:(NSIndexPath *)indexPath
forListAdapter:(IGListAdapter *)listAdapter {
//将将要移除到可见范围之外的cell的NSIndexPath从_visibleSectionIndices中移除
_visibleSectionIndices.erase({
.section = indexPath.section,
.row = indexPath.row,
.hash = indexPath.hash
});
[self _updateWorkingRangesWithListAdapter:listAdapter];
}

#pragma mark - Working Ranges

- (void)_updateWorkingRangesWithListAdapter:(IGListAdapter *)listAdapter {
IGAssertMainThread();
// This method is optimized C++ to improve straight-line speed of these operations. Change at your peril.
// We use a std::set because it is ordered.
std::set<NSInteger> visibleSectionSet = std::set<NSInteger>();
for (const _IGListWorkingRangeHandlerIndexPath &indexPath : _visibleSectionIndices) {
visibleSectionSet.insert(indexPath.section);
}

//根据workingRangeSize计算workingRange的start 和 end
NSInteger start;
NSInteger end;
if (visibleSectionSet.size() == 0) {
// We're now devoid of any visible sections. Bail
start = 0;
end = 0;
} else {
start = MAX(*visibleSectionSet.begin() - _workingRangeSize, 0);
end = MIN(*visibleSectionSet.rbegin() + 1 + _workingRangeSize, (NSInteger)listAdapter.objects.count);
}

// Build the current set of working range section controllers
// 构建新的可见sessionController 集合
_IGListWorkingRangeSectionControllerSet workingRangeSectionControllers (visibleSectionSet.size());
for (NSInteger idx = start; idx < end; idx++) {
//取出工作区中的sessionController
id item = [listAdapter objectAtSection:idx];
IGListSectionController *sectionController = [listAdapter sectionControllerForObject:item];
workingRangeSectionControllers.insert({sectionController});
}

//工作区不能大于1000
IGAssert(workingRangeSectionControllers.size() < 1000, @"This algorithm is way too slow with so many objects:%lu", workingRangeSectionControllers.size());

// Tell any new section controllers that they have entered the working range
// 从新的可见sessionController 集合中遍历每个元素看下是否在旧的存在,如果在旧的不存在 说明这个是新进入工作区的通知对应的sessionController
for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : workingRangeSectionControllers) {
// Check if the item exists in the old working range item array.
auto it = _workingRangeSectionControllers.find(wrapper);
if (it == _workingRangeSectionControllers.end()) {
// The section controller isn't in the existing list, so it's new.
id <IGListWorkingRangeDelegate> workingRangeDelegate = wrapper.sectionController.workingRangeDelegate;
[workingRangeDelegate listAdapter:listAdapter sectionControllerWillEnterWorkingRange:wrapper.sectionController];
}
}

// 同理处理退出工作区的事件
// Tell any removed section controllers that they have exited the working range
for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : _workingRangeSectionControllers) {
// Check if the item exists in the new list of section controllers
auto it = workingRangeSectionControllers.find(wrapper);
if (it == workingRangeSectionControllers.end()) {
// If the item does not exist in the new list, then it's been removed.
id <IGListWorkingRangeDelegate> workingRangeDelegate = wrapper.sectionController.workingRangeDelegate;
[workingRangeDelegate listAdapter:listAdapter sectionControllerDidExitWorkingRange:wrapper.sectionController];
}
}

_workingRangeSectionControllers = workingRangeSectionControllers;
}

加载数据到UICollectionView

- (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion {

id<IGListAdapterDataSource> dataSource = self.dataSource;
UICollectionView *collectionView = self.collectionView;
//.......
//从数据源里面获取当前adapter的全部数据集
NSArray *uniqueObjects = objectsWithDuplicateIdentifiersRemoved([dataSource objectsForListAdapter:self]);
__weak __typeof__(self) weakSelf = self;
//调用updater 加载数据
[self.updater reloadDataWithCollectionViewBlock:[self _collectionViewBlock]
reloadUpdateBlock:^{
// purge all section controllers from the item map so that they are regenerated
[weakSelf.sectionMap reset];
[weakSelf _updateObjects:uniqueObjects dataSource:dataSource];
} completion:^(BOOL finished) {
[weakSelf _notifyDidUpdate:IGListAdapterUpdateTypeReloadData animated:NO];
if (completion) {
completion(finished);
}
}];
}

在adapter调用加载数据的时候会先从datasource中获取到当前adapter全部sessionConstroller所需要的全部数据集。这些数据数据集被包裹reloadUpdateBlock,等到需要执行reloadUpdateBlock的时候更新Adapter里面的sessionMap等相关等数据集合,因为后面调用UICollectionView reloadData后会通过UICollectionView 的dateSource也就是 adapter中通过对应的代理方法从sessionController中获取数据。

完成上面的设置后就可以调用updater的reloadDataWithCollectionViewBlock进行数据加载。

- (void)reloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock
completion:(nullable IGListUpdatingCompletion)completion {

//.......
//将完成所需要调用的结束回调添加到completionBlocks
IGListUpdatingCompletion localCompletion = completion;
if (localCompletion) {
[self.completionBlocks addObject:localCompletion];
}
self.reloadUpdates = reloadUpdateBlock;
self.queuedReloadData = YES;
[self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
}

reloadDataWithCollectionViewBlock中会将queuedReloadData设置为YES,然后触发_queueUpdateWithCollectionViewBlock,在_queueUpdateWithCollectionViewBlock中由于queuedReloadData设置为YES,所以执行performReloadDataWithCollectionViewBlock


- (void)_queueUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
//.....
dispatch_async(dispatch_get_main_queue(), ^{
//如果正在处于批量更新数据中,或者没有新的变更则直接返回
if (weakSelf.state != IGListBatchUpdateStateIdle
|| ![weakSelf hasChanges]) {
return;
}
if (weakSelf.hasQueuedReloadData) {
//执行加载
[weakSelf performReloadDataWithCollectionViewBlock:collectionViewBlock];
} else {
//批量加载
[weakSelf performBatchUpdatesWithCollectionViewBlock:collectionViewBlock];
}
});
}

加载数据到UICollectionView

- (void)performReloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
IGAssertMainThread();

id<IGListAdapterUpdaterDelegate> delegate = self.delegate;
void (^reloadUpdates)(void) = self.reloadUpdates;
IGListBatchUpdates *batchUpdates = self.batchUpdates;
NSMutableArray *completionBlocks = [self.completionBlocks mutableCopy];

//开始加载前清除相关的状态变量
[self cleanStateBeforeUpdates];

//加载结束之后执行全部的completeBlock,并且将状态改为IGListBatchUpdateStateIdle
void (^executeCompletionBlocks)(BOOL) = ^(BOOL finished) {
for (IGListUpdatingCompletion block in completionBlocks) {
block(finished);
}
self.state = IGListBatchUpdateStateIdle;
};
// 在更新任务还在队列的时候如果返回执行的时候发现collection已经被释放则重置对应的状态并调用completeBlock,将状态设置为IGListBatchUpdateStateIdle
UICollectionView *collectionView = collectionViewBlock();
if (collectionView == nil) {
[self _cleanStateAfterUpdates];
executeCompletionBlocks(NO);
return;
}

// item updates must not send mutations to the collection view while we are reloading
// 将状态设置为正在加载数据
self.state = IGListBatchUpdateStateExecutingBatchUpdateBlock;

//调用_updateObjects 重建object 和 sessionController之间的关系
if (reloadUpdates) {
reloadUpdates();
}

// 执行批量更新中的单个更新block
// execute all stored item update blocks even if we are just calling reloadData. the actual collection view
// mutations will be discarded, but clients are encouraged to put their actual /data/ mutations inside the
// update block as well, so if we don't execute the block the changes will never happen
for (IGListItemUpdateBlock itemUpdateBlock in batchUpdates.itemUpdateBlocks) {
itemUpdateBlock();
}
[completionBlocks addObjectsFromArray:batchUpdates.itemCompletionBlocks];
self.state = IGListBatchUpdateStateExecutedBatchUpdateBlock;
//将状态设置为批量更新执行完毕
[self _cleanStateAfterUpdates];
//通知外部当前的UICollectionView即将开始加载数据
[delegate listAdapterUpdater:self willReloadDataWithCollectionView:collectionView];
//加载数据
[collectionView reloadData];
[collectionView.collectionViewLayout invalidateLayout];
[collectionView layoutIfNeeded];
//通知外部当前的UICollectionView已经加载完数据
[delegate listAdapterUpdater:self didReloadDataWithCollectionView:collectionView];

//执行完成后的block
executeCompletionBlocks(YES);
}

performReloadDataWithCollectionViewBlock方法中会执行传入的reloadUpdates以及batchUpdates.itemUpdateBlocks中对应的更新block然后在调用对应的
delegate 通知UICollectionView将要开始进行数据加载。然后调用[collectionView reloadData]加载数据。

调用[collectionView reloadData] 后便会触发UICollectionView的对应代理,由于IGListAdapter会对UICollectionView 的UICollectionViewDataSource 代理进行拦截,所以这些处理出现在IGListAdapter中:

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return self.sectionMap.objects.count;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
IGListSectionController * sectionController = [self sectionControllerForSection:section];
IGAssert(sectionController != nil, @"Nil section controller for section %li for item %@. Check your -diffIdentifier and -isEqual: implementations.",
(long)section, [self.sectionMap objectForSection:section]);
const NSInteger numberOfItems = [sectionController numberOfItems];
IGAssert(numberOfItems >= 0, @"Cannot return negative number of items %li for section controller %@.", (long)numberOfItems, sectionController);
return numberOfItems;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
id<IGListAdapterPerformanceDelegate> performanceDelegate = self.performanceDelegate;
[performanceDelegate listAdapterWillCallDequeueCell:self];
IGListSectionController *sectionController = [self sectionControllerForSection:indexPath.section];
// flag that a cell is being dequeued in case it tries to access a cell in the process
_isDequeuingCell = YES;
UICollectionViewCell *cell = [sectionController cellForItemAtIndex:indexPath.item];
_isDequeuingCell = NO;
IGAssert(cell != nil, @"Returned a nil cell at indexPath <%@> from section controller: <%@>", indexPath, sectionController);
// associate the section controller with the cell so that we know which section controller is using it
[self mapView:cell toSectionController:sectionController];
[performanceDelegate listAdapter:self didCallDequeueCell:cell onSectionController:sectionController atIndex:indexPath.item];
return cell;
}

这些方法大致思路是通过indexPath获取到对应的SessionController然后再通过SessionController中的方法获取对应的数据。

批量更新UICollectionView数据

- (void)performUpdatesAnimated:(BOOL)animated completion:(IGListUpdaterCompletion)completion {

//.......
id<IGListAdapterDataSource> dataSource = self.dataSource;
UICollectionView *collectionView = self.collectionView;
//....
//旧的数据源
NSArray *fromObjects = self.sectionMap.objects;

IGListToObjectBlock toObjectsBlock;
__weak __typeof__(self) weakSelf = self;
if (IGListExperimentEnabled(self.experiments, IGListExperimentDeferredToObjectCreation)) {
toObjectsBlock = ^NSArray *{
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return nil;
}
return [dataSource objectsForListAdapter:strongSelf];
};
} else {
//从dataSource中获取最新的数据源
NSArray *newObjects = [dataSource objectsForListAdapter:self];
toObjectsBlock = ^NSArray *{
return newObjects;
};
}

[self _enterBatchUpdates];
[self.updater performUpdateWithCollectionViewBlock:[self _collectionViewBlock]
fromObjects:fromObjects
toObjectsBlock:toObjectsBlock
animated:animated
objectTransitionBlock:^(NSArray *toObjects) {
// 更新数据源
weakSelf.previousSectionMap = [weakSelf.sectionMap copy];
[weakSelf _updateObjects:toObjects dataSource:dataSource];
} completion:^(BOOL finished) {
weakSelf.previousSectionMap = nil;
[weakSelf _notifyDidUpdate:IGListAdapterUpdateTypePerformUpdates animated:animated];
if (completion) {
completion(finished);
}
[weakSelf _exitBatchUpdates];
}];
}
- (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
fromObjects:(NSArray *)fromObjects
toObjectsBlock:(IGListToObjectBlock)toObjectsBlock
animated:(BOOL)animated
objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock
completion:(IGListUpdatingCompletion)completion {

//........
self.fromObjects = self.fromObjects ?: self.pendingTransitionToObjects ?: fromObjects;
self.toObjectsBlock = toObjectsBlock;

self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated;
self.objectTransitionBlock = objectTransitionBlock;

IGListUpdatingCompletion localCompletion = completion;
if (localCompletion) {
[self.completionBlocks addObject:localCompletion];
}
[self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
}

- (void)_queueUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
if (weakSelf.state != IGListBatchUpdateStateIdle
|| ![weakSelf hasChanges]) {
return;
}
if (weakSelf.hasQueuedReloadData) {
[weakSelf performReloadDataWithCollectionViewBlock:collectionViewBlock];
} else {
//更新的时候走这里
[weakSelf performBatchUpdatesWithCollectionViewBlock:collectionViewBlock];
}
});
}

- (void)performBatchUpdatesWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
//........
// create local variables so we can immediately clean our state but pass these items into the batch update block
id<IGListAdapterUpdaterDelegate> delegate = self.delegate;
NSArray *fromObjects = [self.fromObjects copy];
IGListToObjectBlock toObjectsBlock = [self.toObjectsBlock copy];
NSMutableArray *completionBlocks = [self.completionBlocks mutableCopy];
void (^objectTransitionBlock)(NSArray *) = [self.objectTransitionBlock copy];
const BOOL animated = self.queuedUpdateIsAnimated;
IGListBatchUpdates *batchUpdates = self.batchUpdates;

// 在更新前重置状态为初始状态
// clean up all state so that new updates can be coalesced while the current update is in flight
[self cleanStateBeforeUpdates];

// 更新完成之后的回调
void (^executeCompletionBlocks)(BOOL) = ^(BOOL finished) {
self.applyingUpdateData = nil;
self.state = IGListBatchUpdateStateIdle;
for (IGListUpdatingCompletion block in completionBlocks) {
block(finished);
}
};

//.......
NSArray *toObjects = nil;
if (toObjectsBlock != nil) {
//剔除掉重复数据
toObjects = objectsWithDuplicateIdentifiersRemoved(toObjectsBlock());
}

//数据刷新的闭包
void (^executeUpdateBlocks)(void) = ^{
self.state = IGListBatchUpdateStateExecutingBatchUpdateBlock;
// 更新包括 IGListAdapter 的 sectionController 和 objects 的映射关系等数据,保证执行刷新前数据已经是最新的
if (objectTransitionBlock != nil) {
objectTransitionBlock(toObjects);
}
// 触发批量刷新任务的数据更新闭包(包括插入、删除、刷新单个 section 的数据)objectTransitionBlock 之后执行是为了保证 section 级别的刷新在 item 级别刷新之前进行
for (IGListItemUpdateBlock itemUpdateBlock in batchUpdates.itemUpdateBlocks) {
itemUpdateBlock();
}

//收集批量刷新完成的回调,后续所有操作完了之后一并处理
[completionBlocks addObjectsFromArray:batchUpdates.itemCompletionBlocks];

self.state = IGListBatchUpdateStateExecutedBatchUpdateBlock;
};

// 执行全量的数据更新并刷新 UI
void (^reloadDataFallback)(void) = ^{
//执行更新
executeUpdateBlocks();
//清除batchUpdates
[self _cleanStateAfterUpdates];
[self _performBatchUpdatesItemBlockApplied];
//加载数据
[collectionView reloadData];
[collectionView layoutIfNeeded];
executeCompletionBlocks(YES);
};

if (self.allowsBackgroundReloading && collectionView.window == nil) {
[self _beginPerformBatchUpdatesToObjects:toObjects];
reloadDataFallback();
return;
}
// disables multiple performBatchUpdates: from happening at the same time
[self _beginPerformBatchUpdatesToObjects:toObjects];

const IGListExperiment experiments = self.experiments;

IGListIndexSetResult *(^performDiff)(void) = ^{
return IGListDiffExperiment(fromObjects, toObjects, IGListDiffEquality, experiments);
};

// block executed in the first param block of -[UICollectionView performBatchUpdates:completion:]
void (^batchUpdatesBlock)(IGListIndexSetResult *result) = ^(IGListIndexSetResult *result){
//执行更新
executeUpdateBlocks();
self.applyingUpdateData = [self _flushCollectionView:collectionView
withDiffResult:result
batchUpdates:self.batchUpdates
fromObjects:fromObjects];
//重置状态
[self _cleanStateAfterUpdates];
[self _performBatchUpdatesItemBlockApplied];
};

// block used as the second param of -[UICollectionView performBatchUpdates:completion:]
void (^batchUpdatesCompletionBlock)(BOOL) = ^(BOOL finished) {
IGListBatchUpdateData *oldApplyingUpdateData = self.applyingUpdateData;
executeCompletionBlocks(finished);

[delegate listAdapterUpdater:self didPerformBatchUpdates:oldApplyingUpdateData collectionView:collectionView];

// queue another update in case something changed during batch updates. this method will bail next runloop if
// there are no changes
[self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
};

// block that executes the batch update and exception handling
void (^performUpdate)(IGListIndexSetResult *) = ^(IGListIndexSetResult *result){
[collectionView layoutIfNeeded];

@try {
// 对外通知即将进行 batch update
[delegate listAdapterUpdater:self
willPerformBatchUpdatesWithCollectionView:collectionView
fromObjects:fromObjects
toObjects:toObjects
listIndexSetResult:result];
if (collectionView.dataSource == nil) {
// 如果数据源为空则不再刷新的 UICollectionview
batchUpdatesCompletionBlock(NO);
} else if (result.changeCount > 100 && IGListExperimentEnabled(experiments, IGListExperimentReloadDataFallback)) {
// 如果变化数量超过100,进行全量刷新
reloadDataFallback();
} else if (animated) {
//执行更新的批量动画
[collectionView performBatchUpdates:^{
batchUpdatesBlock(result);
} completion:batchUpdatesCompletionBlock];
} else {
[CATransaction begin];
[CATransaction setDisableActions:YES];
[collectionView performBatchUpdates:^{
batchUpdatesBlock(result);
} completion:^(BOOL finished) {
[CATransaction commit];
batchUpdatesCompletionBlock(finished);
}];
}
} @catch (NSException *exception) {
[delegate listAdapterUpdater:self
collectionView:collectionView
willCrashWithException:exception
fromObjects:fromObjects
toObjects:toObjects
diffResult:result
updates:(id)self.applyingUpdateData];
@throw exception;
}
};

if (IGListExperimentEnabled(experiments, IGListExperimentBackgroundDiffing)) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
IGListIndexSetResult *result = performDiff();
dispatch_async(dispatch_get_main_queue(), ^{
performUpdate(result);
});
});
} else {
IGListIndexSetResult *result = performDiff();
performUpdate(result);
}
}

批量更新这部分细节有点多,这里归纳如下:

如果allowsBackgroundReloading为YES,也就是说允许后台加载数据的情况下如果collectionView不显示,则跳过数据diff和批量更新直接进行全量加载刷新。否则在子线程调用performDiff() 通过 IGListDiffExperiment 计算数据的变化,然后在主线程调用performUpdate进行批量更新操作:
在performUpdate开始的时候先通过代理对外通知即将进行 batch update 批量更新,紧接着判断如果 collectionView 的 dataSource 为 nil,结束更新过程,如果变化的数据个数超过100,就不进行批量更新了直接调用 reloadData 全量刷新数据;如果变化数据小于100,则调用 -[UICollectionView performBatchUpdates:completion:] 批量刷新数据,刷新过程中会调用 -_flushCollectionView:withDiffResult:batchUpdates:fromObjects: 将数据源提供的数据和 diff 结果包装成批量更新的数据类型 IGListBatchUpdateData 以便 UICollectionView 进行读取。这里最关键的代码在****_flushCollectionView****

- (IGListBatchUpdateData *)_flushCollectionView:(UICollectionView *)collectionView
withDiffResult:(IGListIndexSetResult *)diffResult
batchUpdates:(IGListBatchUpdates *)batchUpdates
fromObjects:(NSArray <id<IGListDiffable>> *)fromObjects {
//移动session
NSSet *moves = [[NSSet alloc] initWithArray:diffResult.moves];
//reload的item:合并通过diff和通过reloadItems手动reloads的session
NSMutableIndexSet *reloads = [diffResult.updates mutableCopy];
[reloads addIndexes:batchUpdates.sectionReloads];
//插入的session
NSMutableIndexSet *inserts = [diffResult.inserts mutableCopy];
//删除的session
NSMutableIndexSet *deletes = [diffResult.deletes mutableCopy];
NSMutableArray<NSIndexPath *> *itemUpdates = [NSMutableArray new];
//如果movesAsDeletesInserts = YES 那么就将moves中的转换为deletes和inserts操作
if (self.movesAsDeletesInserts) {
for (IGListMoveIndex *move in moves) {
[deletes addIndex:move.from];
[inserts addIndex:move.to];
}
// clear out all moves
moves = [NSSet new];
}

// Item reloads are not safe, if any section moves happened or there are inserts/deletes.
if (self.preferItemReloadsForSectionReloads
&& moves.count == 0 && inserts.count == 0 && deletes.count == 0 && reloads.count > 0) {
[reloads enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL * _Nonnull stop) {
NSMutableIndexSet *localIndexSet = [NSMutableIndexSet indexSetWithIndex:sectionIndex];
if (sectionIndex < [collectionView numberOfSections]
&& sectionIndex < [collectionView.dataSource numberOfSectionsInCollectionView:collectionView]
&& [collectionView numberOfItemsInSection:sectionIndex] == [collectionView.dataSource collectionView:collectionView numberOfItemsInSection:sectionIndex]) {
// Perfer to do item reloads instead, if the number of items in section is unchanged.
[itemUpdates addObjectsFromArray:convertSectionReloadToItemUpdates(localIndexSet, collectionView)];
} else {
// Otherwise, fallback to convert into delete+insert section operation.
convertReloadToDeleteInsert(localIndexSet, deletes, inserts, diffResult, fromObjects);
}
}];
} else {
// 在performBatchUpdates中使用reloadSections是不安全的,所以需要将reloads转换为deletes+inserts
convertReloadToDeleteInsert(reloads, deletes, inserts, diffResult, fromObjects);
}

//插入,删除,移动 item处理
NSMutableArray<NSIndexPath *> *itemInserts = batchUpdates.itemInserts;
NSMutableArray<NSIndexPath *> *itemDeletes = batchUpdates.itemDeletes;
NSMutableArray<IGListMoveIndexPath *> *itemMoves = batchUpdates.itemMoves;

NSSet<NSIndexPath *> *uniqueDeletes = [NSSet setWithArray:itemDeletes];
NSMutableSet<NSIndexPath *> *reloadDeletePaths = [NSMutableSet new];
NSMutableSet<NSIndexPath *> *reloadInsertPaths = [NSMutableSet new];
for (IGListReloadIndexPath *reload in batchUpdates.itemReloads) {
if (![uniqueDeletes containsObject:reload.fromIndexPath]) {
[reloadDeletePaths addObject:reload.fromIndexPath];
[reloadInsertPaths addObject:reload.toIndexPath];
}
}
[itemDeletes addObjectsFromArray:[reloadDeletePaths allObjects]];
[itemInserts addObjectsFromArray:[reloadInsertPaths allObjects]];

const BOOL fixIndexPathImbalance = IGListExperimentEnabled(self.experiments, IGListExperimentFixIndexPathImbalance);
IGListBatchUpdateData *updateData = [[IGListBatchUpdateData alloc] initWithInsertSections:inserts
deleteSections:deletes
moveSections:moves
insertIndexPaths:itemInserts
deleteIndexPaths:itemDeletes
updateIndexPaths:itemUpdates
moveIndexPaths:itemMoves
fixIndexPathImbalance:fixIndexPathImbalance];
[collectionView ig_applyBatchUpdateData:updateData];
return updateData;
}

- (void)ig_applyBatchUpdateData:(IGListBatchUpdateData *)updateData {
[self deleteItemsAtIndexPaths:updateData.deleteIndexPaths];
[self insertItemsAtIndexPaths:updateData.insertIndexPaths];
[self reloadItemsAtIndexPaths:updateData.updateIndexPaths];

for (IGListMoveIndexPath *move in updateData.moveIndexPaths) {
[self moveItemAtIndexPath:move.from toIndexPath:move.to];
}

for (IGListMoveIndex *move in updateData.moveSections) {
[self moveSection:move.from toSection:move.to];
}

[self deleteSections:updateData.deleteSections];
[self insertSections:updateData.insertSections];
}


在_flushCollectionView中会对手动添加的批量操作和通过diff算法确定的更新操作统一转换为对session的insert,delete,reload的操作,最后调用UICollectionView的deleteItemsAtIndexPathsinsertItemsAtIndexPathsreloadItemsAtIndexPathsmoveItemAtIndexPathmoveSectiondeleteSectionsinsertSections。对UICollectionView进行更新。

IGListSectionController

IGListSectionController 在IGListKit中也是一个十分关键的对象,我们先来看下它拥有的属性:

@interface IGListSectionController : NSObject

/**
Session中的item数量
*/
- (NSInteger)numberOfItems;

/**
指定index位置上的item尺寸
*/
- (CGSize)sizeForItemAtIndex:(NSInteger)index;

/**
返回指定位置的Cell对象
*/
- (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index;

/**
从外界注入该Session所拥有的整个对象数组
*/
- (void)didUpdateToObject:(id)object;


- (void)didSelectItemAtIndex:(NSInteger)index;
- (void)didDeselectItemAtIndex:(NSInteger)index;
- (void)didHighlightItemAtIndex:(NSInteger)index;
- (void)didUnhighlightItemAtIndex:(NSInteger)index;


- (BOOL)canMoveItemAtIndex:(NSInteger)index;
- (void)moveObjectFromIndex:(NSInteger)sourceIndex toIndex:(NSInteger)destinationIndex NS_AVAILABLE_IOS(9_0);

/**
持有创建该SessionController的Adapter的ViewController
*/
@property (nonatomic, weak, nullable, readonly) UIViewController *viewController;

/**
与collectionView 交互的上下文
Use this property for accessing the collection size, dequeuing cells, reloading, inserting, deleting, etc.
*/
@property (nonatomic, weak, nullable, readonly) id <IGListCollectionContext> collectionContext;

/**
当前sessionController对应的session
*/
@property (nonatomic, assign, readonly) NSInteger section;


@property (nonatomic, assign, readonly) BOOL isFirstSection;
@property (nonatomic, assign, readonly) BOOL isLastSection;

@property (nonatomic, assign) UIEdgeInsets inset;
@property (nonatomic, assign) CGFloat minimumLineSpacing;
@property (nonatomic, assign) CGFloat minimumInteritemSpacing;

@property (nonatomic, weak, nullable) id <IGListSupplementaryViewSource> supplementaryViewSource;

/**
section controller 的显示事件代理
*/
@property (nonatomic, weak, nullable) id <IGListDisplayDelegate> displayDelegate;

/**
section controller 的working range事件代理
*/
@property (nonatomic, weak, nullable) id <IGListWorkingRangeDelegate> workingRangeDelegate;

/**
section controller 的滚动事件代理
*/
@property (nonatomic, weak, nullable) id <IGListScrollDelegate> scrollDelegate;

@property (nonatomic, weak, nullable) id<IGListTransitionDelegate> transitionDelegate;

@end

从上面上看外部主要通过didUpdateToObject将数据注入到IGListSectionController,然后通过numberOfItemssizeForItemAtIndexcellForItemAtIndexsupplementaryViewSource 分别指定item数量,尺寸,cell实例,SessionController的header,footer。以及通过collectionContext和UICollectionView进行交互,

我们最后来看下IGListCollectionContext,它也是一个比较重要的类,我们在确定sessionController的时候用得比较多。

@protocol IGListCollectionContext <NSObject>

@property (nonatomic, readonly) CGSize containerSize;
@property (nonatomic, readonly) UIEdgeInsets containerInset;
@property (nonatomic, readonly) UIEdgeInsets adjustedContainerInset;
@property (nonatomic, readonly) CGSize insetContainerSize;

- (CGSize)containerSizeForSectionController:(IGListSectionController *)sectionController;

@property (nonatomic, readonly) IGListCollectionScrollingTraits scrollingTraits;

@property (nonatomic, assign) IGListExperiment experiments;

- (NSInteger)indexForCell:(UICollectionViewCell *)cell
sectionController:(IGListSectionController *)sectionController;
- (nullable __kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index
sectionController:(IGListSectionController *)sectionController;

- (NSArray<UICollectionViewCell *> *)visibleCellsForSectionController:(IGListSectionController *)sectionController;
- (NSArray<NSIndexPath *> *)visibleIndexPathsForSectionController:(IGListSectionController *) sectionController;


- (void)deselectItemAtIndex:(NSInteger)index
sectionController:(IGListSectionController *)sectionController
animated:(BOOL)animated;

- (void)selectItemAtIndex:(NSInteger)index
sectionController:(IGListSectionController *)sectionController
animated:(BOOL)animated
scrollPosition:(UICollectionViewScrollPosition)scrollPosition;


- (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass
withReuseIdentifier:(nullable NSString *)reuseIdentifier
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index;
- (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index;
- (__kindof UICollectionViewCell *)dequeueReusableCellWithNibName:(NSString *)nibName
bundle:(nullable NSBundle *)bundle
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index;
- (__kindof UICollectionViewCell *)dequeueReusableCellFromStoryboardWithIdentifier:(NSString *)identifier
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index;
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind
forSectionController:(IGListSectionController *)sectionController
class:(Class)viewClass
atIndex:(NSInteger)index;
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewFromStoryboardOfKind:(NSString *)elementKind
withIdentifier:(NSString *)identifier
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index;
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind
forSectionController:(IGListSectionController *)sectionController
nibName:(NSString *)nibName
bundle:(nullable NSBundle *)bundle
atIndex:(NSInteger)index;


- (void)invalidateLayoutForSectionController:(IGListSectionController *)sectionController
completion:(nullable void (^)(BOOL finished))completion;


- (void)performBatchAnimated:(BOOL)animated
updates:(void (^)(id<IGListBatchContext> batchContext))updates
completion:(nullable void (^)(BOOL finished))completion;

- (void)scrollToSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index
scrollPosition:(UICollectionViewScrollPosition)scrollPosition
animated:(BOOL)animated;

@end
相关文章推荐
Contents
  1. 1. 开源信息
  2. 2. 源码解析
  3. 3. 相关文章推荐