开源信息
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是由哪些部分构成的:
这里大致将整个代码分成两大部分:
IGListDiffKit 里面存放的是IGListKit的diff 算法相关类
IGListKit 里面存放的是整个框架代码,它是由如下几部分构成:
Adapter: 这是最核心的部分,是它将CollectionView,SessionController,Updater,Context关联起来。它从SessionController获取每个session的数据,通过Updater将数据加载到CollectionView。
Updater: 负责将数据加载到CollectionView
CollectionView: 负责数据的展示
Context: 一些环境上下文全局内容
Debug: 用于辅助调试的内容
这篇博客会从数据源开始,从数据源的提供,根据数据源选择SessionController,再通过Updater将数据加载到UICollectionView.
数据源
IGListKit 数据源都必须遵循IGListAdapterDataSource 协议:
- (NSArray<id <IGListDiffable>> *)objectsForListAdapter:(IGListAdapter *)listAdapter; - (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;@property (nonatomic , nullable , weak ) UICollectionView *collectionView;@property (nonatomic , nullable , weak ) id <IGListAdapterDataSource> dataSource;@property (nonatomic , nullable , weak ) id <IGListAdapterDelegate> delegate;@property (nonatomic , nullable , weak ) id <UICollectionViewDelegate > collectionViewDelegate;@property (nonatomic , nullable , weak ) id <UIScrollViewDelegate > scrollViewDelegate;@property (nonatomic , nullable , weak ) id <IGListAdapterMoveDelegate> moveDelegate NS_AVAILABLE_IOS (9 _0);@property (nonatomic , nullable , weak ) id <IGListAdapterPerformanceDelegate> performanceDelegate;@property (nonatomic , strong , readonly ) id <IGListUpdatingDelegate> updater;@property (nonatomic , strong , readonly ) IGListSectionMap *sectionMap;@property (nonatomic , strong , readonly ) IGListDisplayHandler *displayHandler;@property (nonatomic , strong , readonly ) IGListWorkingRangeHandler *workingRangeHandler;@property (nonatomic , strong , nullable ) IGListAdapterProxy *delegateProxy;@property (nonatomic , strong , nullable ) UIView *emptyBackgroundView;@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 :负责可见对象的控制,这些可见对象存储于visibleViewObjectMapIGListWorkingRangeHandler :负责工作区的管理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 { if (_collectionView != collectionView || _collectionView.dataSource != self ) { 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]; _registeredCellIdentifiers = [NSMutableSet new]; _registeredNibNames = [NSMutableSet new]; _registeredSupplementaryViewIdentifiers = [NSMutableSet new]; _registeredSupplementaryViewNibNames = [NSMutableSet new]; const BOOL settingFirstCollectionView = _collectionView == nil ; _collectionView = collectionView; _collectionView.dataSource = self ; [_collectionView.collectionViewLayout ig_hijackLayoutInteractiveReorderingMethodForAdapter:self ]; [_collectionView.collectionViewLayout invalidateLayout]; [self _updateCollectionViewDelegate]; 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 ) { 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; NSMutableSet *updatedObjects = [NSMutableSet new]; for (id object in objects) { IGListSectionController *sectionController = [map sectionControllerForObject:object]; 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.collectionContext = self ; sectionController.viewController = self .viewController; const NSInteger oldSection = [map sectionForObject:object]; if (oldSection == NSNotFound || [map objectForSection:oldSection] != object) { [updatedObjects addObject:object]; } [sectionControllers addObject:sectionController]; [validObjects addObject:object]; } IGListSectionControllerPopThread(); [map updateWithObjects:validObjects sectionControllers:sectionControllers]; for (id object in updatedObjects) { [[map sectionControllerForObject:object] didUpdateToObject:object]; } NSInteger itemCount = 0 ; for (IGListSectionController *sectionController in sectionControllers) { itemCount += [sectionController numberOfItems]; } [self _updateBackgroundViewShouldHide:itemCount > 0 ]; } - (void )_updateBackgroundViewShouldHide:(BOOL )shouldHide { if (self .isInUpdateBlock) { return ; } UIView *backgroundView = [self .dataSource emptyViewForListAdapter:self ]; if (backgroundView != _collectionView.backgroundView) { [_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 { if (_collectionViewDelegate != collectionViewDelegate) { _collectionViewDelegate = collectionViewDelegate; [self _createProxyAndUpdateCollectionViewDelegate ]; } } - (void)setScrollViewDelegate:(id<UIScrollViewDelegate>)scrollViewDelegate { if (_scrollViewDelegate != scrollViewDelegate) { _scrollViewDelegate = scrollViewDelegate; [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 ( sel == @selector (scrollViewDidScroll :) || sel == @selector (scrollViewWillBeginDragging :) || sel == @selector (scrollViewDidEndDragging :willDecelerate :) || sel == @selector (scrollViewDidEndDecelerating :) || 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 :) || 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 :) || sel == @selector (collectionView :layout :customizedInitialLayoutAttributes :atIndexPath :) || sel == @selector (collectionView :layout :customizedFinalLayoutAttributes :atIndexPath :) ); }
Object && sessionController 之间的映射关系
目前在IGListKit可以完成如下几种对象之间的映射关系:
<-- . -- > <--- --- > <-- -- > -- -- -- >
这部分代码就不贴出来了,最底层逻辑在sectionMap上,大家可以查看对应的源码。
可见性通知
这部分主要涉及IGListDisplayDelegate ,IGListAdapterDelegate ,IGListDisplayHandler 这三个类,我们可以指定IGListAdapter的delegate来监听可见性情况:
@protocol IGListAdapterDelegate <NSObject >- (void )listAdapter:(IGListAdapter *)listAdapter willDisplayObject:(id )object atIndex:(NSInteger )index; - (void )listAdapter:(IGListAdapter *)listAdapter didEndDisplayingObject:(id )object atIndex:(NSInteger )index; @end
IGListAdapterDelegate其实和IGListDisplayDelegate是一样的,只不过一个是在adapter上层监听,一个是在sessionController监听。IGListDisplayDelegate其实监听的是更细粒度的可以通过代理知道具体哪个cell进入可见区域,我们在看下IGListDisplayDelegate:
@protocol IGListDisplayDelegate <NSObject >- (void )listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController; - (void )listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController; - (void )listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController cell:(UICollectionViewCell *)cell atIndex:(NSInteger )index; - (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 setObject:object forKey:view]; NSCountedSet *visibleListSections = self .visibleListSections; if ([visibleListSections countForObject:sectionController] == 0 ) { [sectionController.displayDelegate listAdapter:listAdapter willDisplaySectionController:sectionController]; [listAdapter.delegate listAdapter:listAdapter willDisplayObject:object atIndex:indexPath.section]; } [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; 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 ]; id< UICollectionViewDelegate > collectionViewDelegate = self .collectionViewDelegate; if ([collectionViewDelegate respondsToSelector:@selector (collectionView:willDisplayCell:forItemAtIndexPath:)]) { [collectionViewDelegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; } IGListSectionController * sectionController = [self sectionControllerForView:cell]; if (sectionController == nil ) { sectionController = [self sectionControllerForSection:indexPath.section]; [self mapView:cell toSectionController:sectionController]; } id object = [self .sectionMap objectForSection:indexPath.section]; [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 ]; id< UICollectionViewDelegate > collectionViewDelegate = self .collectionViewDelegate; if ([collectionViewDelegate respondsToSelector:@selector (collectionView:didEndDisplayingCell:forItemAtIndexPath:)]) { [collectionViewDelegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath]; } IGListSectionController * sectionController = [self sectionControllerForView:cell]; [self .displayHandler didEndDisplayingCell:cell forListAdapter:self sectionController:sectionController indexPath:indexPath]; [self .workingRangeHandler didEndDisplayingItemAtIndexPath:indexPath forListAdapter:self ]; [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 { 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 (sectionController == nil ) { sectionController = [self .sectionMap sectionControllerForSection:indexPath.section]; [self mapView:view toSectionController:sectionController]; } id object = [self .sectionMap objectForSection:indexPath.section]; [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 { 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]; [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 .insert ({ .section = indexPath.section, .row = indexPath.row, .hash = indexPath.hash }); [self _updateWorkingRangesWithListAdapter :listAdapter]; } - (void)didEndDisplayingItemAtIndexPath:(NSIndexPath *)indexPath forListAdapter:(IGListAdapter *)listAdapter { _visibleSectionIndices .erase({ .section = indexPath.section, .row = indexPath.row, .hash = indexPath.hash }); [self _updateWorkingRangesWithListAdapter :listAdapter]; } #pragma mark - Working Ranges - (void)_updateWorkingRangesWithListAdapter :(IGListAdapter *)listAdapter { IGAssertMainThread(); std::set <NSInteger> visibleSectionSet = std::set <NSInteger>(); for (const _IGListWorkingRangeHandlerIndexPath &indexPath : _visibleSectionIndices ) { visibleSectionSet.insert (indexPath.section); } NSInteger start; NSInteger end; if (visibleSectionSet.size () == 0 ) { start = 0 ; end = 0 ; } else { start = MAX (*visibleSectionSet.begin() - _workingRangeSize , 0 ); end = MIN (*visibleSectionSet.rbegin() + 1 + _workingRangeSize , (NSInteger)listAdapter.objects.count ); } _IGListWorkingRangeSectionControllerSet workingRangeSectionControllers (visibleSectionSet.size ()); for (NSInteger idx = start; idx < end; idx++) { id item = [listAdapter objectAtSection:idx]; IGListSectionController *sectionController = [listAdapter sectionControllerForObject:item]; workingRangeSectionControllers.insert ({sectionController}); } IGAssert(workingRangeSectionControllers.size () < 1000 , @"This algorithm is way too slow with so many objects:%lu" , workingRangeSectionControllers.size ()); for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : workingRangeSectionControllers) { auto it = _workingRangeSectionControllers .find (wrapper); if (it == _workingRangeSectionControllers .end()) { id <IGListWorkingRangeDelegate> workingRangeDelegate = wrapper.sectionController.workingRangeDelegate; [workingRangeDelegate listAdapter:listAdapter sectionControllerWillEnterWorkingRange:wrapper.sectionController]; } } for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : _workingRangeSectionControllers ) { auto it = workingRangeSectionControllers.find (wrapper); if (it == workingRangeSectionControllers.end()) { 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; NSArray *uniqueObjects = objectsWithDuplicateIdentifiersRemoved([dataSource objectsForListAdapter:self ]); __weak __typeof__(self ) weakSelf = self ; [self .updater reloadDataWithCollectionViewBlock:[self _collectionViewBlock] reloadUpdateBlock:^{ [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 { 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]; void (^executeCompletionBlocks)(BOOL ) = ^(BOOL finished) { for (IGListUpdatingCompletion block in completionBlocks) { block(finished); } self .state = IGListBatchUpdateStateIdle; }; UICollectionView *collectionView = collectionViewBlock(); if (collectionView == nil ) { [self _cleanStateAfterUpdates]; executeCompletionBlocks(NO ); return ; } self .state = IGListBatchUpdateStateExecutingBatchUpdateBlock; if (reloadUpdates) { reloadUpdates(); } for (IGListItemUpdateBlock itemUpdateBlock in batchUpdates.itemUpdateBlocks) { itemUpdateBlock(); } [completionBlocks addObjectsFromArray:batchUpdates.itemCompletionBlocks]; self .state = IGListBatchUpdateStateExecutedBatchUpdateBlock; [self _cleanStateAfterUpdates]; [delegate listAdapterUpdater:self willReloadDataWithCollectionView:collectionView]; [collectionView reloadData]; [collectionView.collectionViewLayout invalidateLayout]; [collectionView layoutIfNeeded]; [delegate listAdapterUpdater:self didReloadDataWithCollectionView:collectionView]; 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]; _isDequeuingCell = YES ; UICollectionViewCell *cell = [sectionController cellForItemAtIndex:indexPath.item]; _isDequeuingCell = NO ; IGAssert(cell != nil , @"Returned a nil cell at indexPath <%@> from section controller: <%@>" , indexPath, sectionController); [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 { 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 from Objects:(NSArray *)from Objects to ObjectsBlock:(IGListToObjectBlock)to ObjectsBlock animated:(BOOL)animated objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock completion:(IGListUpdatingCompletion)completion { //........ self .from Objects = self .from Objects ?: self .pendingTransitionToObjects ?: from Objects; self .to ObjectsBlock = to ObjectsBlock; 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 { 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; [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; if (objectTransitionBlock != nil ) { objectTransitionBlock(toObjects); } for (IGListItemUpdateBlock itemUpdateBlock in batchUpdates.itemUpdateBlocks) { itemUpdateBlock(); } [completionBlocks addObjectsFromArray:batchUpdates.itemCompletionBlocks]; self .state = IGListBatchUpdateStateExecutedBatchUpdateBlock; }; void (^reloadDataFallback)(void ) = ^{ executeUpdateBlocks(); [self _cleanStateAfterUpdates]; [self _performBatchUpdatesItemBlockApplied]; [collectionView reloadData]; [collectionView layoutIfNeeded]; executeCompletionBlocks(YES ); }; if (self .allowsBackgroundReloading && collectionView.window == nil ) { [self _beginPerformBatchUpdatesToObjects:toObjects]; reloadDataFallback(); return ; } [self _beginPerformBatchUpdatesToObjects:toObjects]; const IGListExperiment experiments = self .experiments; IGListIndexSetResult *(^performDiff)(void ) = ^{ return IGListDiffExperiment(fromObjects, toObjects, IGListDiffEquality, experiments); }; void (^batchUpdatesBlock)(IGListIndexSetResult *result) = ^(IGListIndexSetResult *result){ executeUpdateBlocks(); self .applyingUpdateData = [self _flushCollectionView:collectionView withDiffResult:result batchUpdates:self .batchUpdates fromObjects:fromObjects]; [self _cleanStateAfterUpdates]; [self _performBatchUpdatesItemBlockApplied]; }; void (^batchUpdatesCompletionBlock)(BOOL ) = ^(BOOL finished) { IGListBatchUpdateData *oldApplyingUpdateData = self .applyingUpdateData; executeCompletionBlocks(finished); [delegate listAdapterUpdater:self didPerformBatchUpdates:oldApplyingUpdateData collectionView:collectionView]; [self _queueUpdateWithCollectionViewBlock:collectionViewBlock]; }; void (^performUpdate)(IGListIndexSetResult *) = ^(IGListIndexSetResult *result){ [collectionView layoutIfNeeded]; @try { [delegate listAdapterUpdater:self willPerformBatchUpdatesWithCollectionView:collectionView fromObjects:fromObjects toObjects:toObjects listIndexSetResult:result]; if (collectionView.dataSource == nil ) { batchUpdatesCompletionBlock(NO ); } else if (result.changeCount > 100 && IGListExperimentEnabled(experiments, IGListExperimentReloadDataFallback)) { 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 { NSSet * moves = [[NSSet alloc] initWithArray:diffResult.moves]; NSMutableIndexSet * reloads = [diffResult.updates mutableCopy]; [reloads addIndexes:batchUpdates.sectionReloads]; NSMutableIndexSet * inserts = [diffResult.inserts mutableCopy]; NSMutableIndexSet * deletes = [diffResult.deletes mutableCopy]; NSMutableArray <NSIndexPath *> * itemUpdates = [NSMutableArray new]; if (self .movesAsDeletesInserts) { for (IGListMoveIndex * move in moves) { [deletes addIndex:move.from]; [inserts addIndex:move.to]; } moves = [NSSet new]; } 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]) { [itemUpdates addObjectsFromArray:convertSectionReloadToItemUpdates(localIndexSet, collectionView)]; } else { convertReloadToDeleteInsert(localIndexSet, deletes, inserts, diffResult, fromObjects); } }]; } else { convertReloadToDeleteInsert(reloads, deletes, inserts, diffResult, fromObjects); } 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的deleteItemsAtIndexPaths ,insertItemsAtIndexPaths ,reloadItemsAtIndexPaths ,moveItemAtIndexPath ,moveSection ,deleteSections ,insertSections 。对UICollectionView进行更新。
IGListSectionController
IGListSectionController 在IGListKit中也是一个十分关键的对象,我们先来看下它拥有的属性:
@interface IGListSectionController : NSObject - (NSInteger )numberOfItems; - (CGSize )sizeForItemAtIndex:(NSInteger )index; - (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger )index; - (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); @property (nonatomic , weak , nullable , readonly ) UIViewController *viewController;@property (nonatomic , weak , nullable , readonly ) id <IGListCollectionContext> collectionContext;@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;@property (nonatomic , weak , nullable ) id <IGListDisplayDelegate> displayDelegate;@property (nonatomic , weak , nullable ) id <IGListWorkingRangeDelegate> workingRangeDelegate;@property (nonatomic , weak , nullable ) id <IGListScrollDelegate> scrollDelegate;@property (nonatomic , weak , nullable ) id <IGListTransitionDelegate> transitionDelegate;@end
从上面上看外部主要通过didUpdateToObject将数据注入到IGListSectionController,然后通过numberOfItems ,sizeForItemAtIndex ,cellForItemAtIndex ,supplementaryViewSource 分别指定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
相关文章推荐