初始化
IGListAdapter
负责处理 UICollectionView
的 DataSource
和 Delegate
,所有 DataSource/Delegate
的相关方法都会在 IGListAdapter
内部消化完毕,调用方只需要设置 IGListAdapter
的 dataSource
和 collectionView
即可, IGListAdapterDataSource
则负责给 IGListAdapter
提供数据源:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@protocol IGListAdapterDataSource <NSObject>
/// 根据不同的 adapter 返回需要展示在列表中的数据,一般情况下每个 UIViewController 只有一个 adapter
- (NSArray<id <IGListDiffable>> *)objectsForListAdapter:(IGListAdapter *)listAdapter;
/// 根据数据来返回新生成的对应的 IGListSectionController
/// IGListSectionController 应该在这里进行初始化,你也可以在这里传递其它数据给 IGListSectionController 。
/// 当 IGListAdapter 被创建,更新或者重新加载( reloaded )时,会初始化所有数据对应的 IGListSectionController 。
/// IGListSectionController 会进行复用,可以通过 `-[IGListDiffable diffIdentifier]` 来阻止。
- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object;
/// 当 UICollectionView 数据为空时就会显示这个方法返回的 UIView ,如果不想显示,可以直接返回 `nil` 。
- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter;
@end
IGListAdapterDataSource
作用和 UICollectionViewDataSource
类似,只不过设置对象变成了 IGListAdapterDataSource
,而且只需要提供数据源和 IGListSectionController
即可,不需要进行其它配置。
下面来看下 IGListAdapter
的初始化方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (instancetype)initWithUpdater:(id <IGListUpdatingDelegate>)updater
viewController:(UIViewController *)viewController
workingRangeSize:(NSInteger)workingRangeSize {
IGAssertMainThread();
IGParameterAssert(updater);
if (self = [super init]) {
// 1. 使用了 `NSMapTable` 而不是 `NSDictionary` ,因为这里的 `key` 是 `id<IGListDiffable>` 对象,不支持 `NSCopying` 协议,
// 所以使用 `NSMapTable` ,通过 `id <IGListUpdatingDelegate>` 的 `objectLookupPointerFunctions` 方法来自定义 `hashFunction` 和 `isEqualFunction` :
NSPointerFunctions *keyFunctions = [updater objectLookupPointerFunctions];
NSPointerFunctions *valueFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory];
NSMapTable *table = [[NSMapTable alloc] initWithKeyPointerFunctions:keyFunctions valuePointerFunctions:valueFunctions capacity:0];
_sectionMap = [[IGListSectionMap alloc] initWithMapTable:table];
// 2. `IGListDisplayHandler` ,提供 `UICollectionViewCell` 和 `UICollectionReusableView` 显示/隐藏相关的生命周期方法,内部调用 `IGListSectionController` 对应的方法
_displayHandler = [IGListDisplayHandler new];
// 3. `IGListWorkingRangeHandler` ,通过设置 `workingRangeSize` ,可以在 `UICollectionView` 滑动时做一些预处理工作
_workingRangeHandler = [[IGListWorkingRangeHandler alloc] initWithWorkingRangeSize:workingRangeSize];
// 4. `NSHashTable<id<IGListAdapterUpdateListener>> *_updateListeners` ,在 `UICollectionView` 完成更新操作后调用
_updateListeners = [NSHashTable weakObjectsHashTable];
// 5. `NSMapTable<UICollectionReusableView *, IGListSectionController *> *_viewSectionControllerMap` ,
// 维护 `sectionController` 和 `UICollectionReusableView` 映射关系。
_viewSectionControllerMap = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality | NSMapTableStrongMemory
valueOptions:NSMapTableStrongMemory];
_updater = updater;
_viewController = viewController;
[IGListDebugger trackAdapter:self];
}
return self;
}
objectLookupPointerFunctions
的自定义 hashFunction
和 isEqualFunction
,由于 object
已经有了 -diffIdentifier
,所以可以基于这个方法进行判断:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static BOOL IGListIsEqual(const void *a, const void *b, NSUInteger (*size)(const void *item)) {
const id<IGListDiffable, NSObject> left = (__bridge id<IGListDiffable, NSObject>)a;
const id<IGListDiffable, NSObject> right = (__bridge id<IGListDiffable, NSObject>)b;
return [left class] == [right class]
&& [[left diffIdentifier] isEqual:[right diffIdentifier]];
}
// 因为 diff 算法是基于 `-diffIdentifier` 进行计算,所以我们的映射表需要精确匹配这种行为
static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(const void *item)) {
return [[(__bridge id<IGListDiffable>)item diffIdentifier] hash];
}
- (NSPointerFunctions *)objectLookupPointerFunctions {
NSPointerFunctions *functions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory];
functions.hashFunction = IGListIdentifierHash;
functions.isEqualFunction = IGListIsEqual;
return functions;
}
IGListAdapter
作为 IGListKit
的中心调度器,负责串联起 IGListSectionController
, Model
, UICollectionView
和 UICollectionReusableView
之间的关系,在设置 UICollectionView
时, IGListAdapter
就会进行对应的处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
- (void)setCollectionView:(UICollectionView *)collectionView {
IGAssertMainThread();
// 1. 如果在 `Cell` 中设置 `UICollectionView` 时有可能会多次设置 `IGListAdapter` 的 `colleciontView` ,这里做一下判断,防止重复设置;
if (_collectionView != collectionView || _collectionView.dataSource != self) {
// 2. 每次关联 `UICollectionView` 和 `IGListAdapter` ,都需要清空之前的关联,
// 防止旧的 `IGListAdapter` 对 `UICollectionView` 进行更新,
// 相关的 PR 在这里 [Prevent stale adapter:collectionView corruptions](https://github.com/Instagram/IGListKit/pull/517)
// 当在 `Cell` 中进行设置时: `adapter.collectionView = cell.collectionView` ,
// 有可能会有多个 `adapter` 链接到同一个 `collectionView` ,
// 那么就可能会发生旧的 `adapter` 对当前 `UICollectionView` 进行修改的 bug ,
// 所以这里需要对之前的 `adapter` 设置 `collectionView` 为 `nil` ;
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];
// 3. 清空已注册的 `Cell` , `Nib` 等;
_registeredCellIdentifiers = [NSMutableSet new];
_registeredNibNames = [NSMutableSet new];
_registeredSupplementaryViewIdentifiers = [NSMutableSet new];
_registeredSupplementaryViewNibNames = [NSMutableSet new];
const BOOL settingFirstCollectionView = _collectionView == nil;
_collectionView = collectionView;
_collectionView.dataSource = self;
if (@available(iOS 10.0, tvOS 10, *)) {
_collectionView.prefetchingEnabled = NO;
}
[_collectionView.collectionViewLayout ig_hijackLayoutInteractiveReorderingMethodForAdapter:self];
[_collectionView.collectionViewLayout invalidateLayout];
// 4. 设置 `collectionView` 的 `delegate` ;
[self _updateCollectionViewDelegate];
// 5. 如果是第一次设置 `collectionView` 则需要进行一些设置。
if (settingFirstCollectionView) {
[self _updateAfterPublicSettingsChange];
}
}
}
_updateAfterPublicSettingsChange
首先调用 NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable>> *objects)
去除重复的 Objects
:
1
2
3
4
5
6
7
- (void)_updateAfterPublicSettingsChange {
id<IGListAdapterDataSource> dataSource = _dataSource;
if (_collectionView != nil && dataSource != nil) {
NSArray *uniqueObjects = objectsWithDuplicateIdentifiersRemoved([dataSource objectsForListAdapter:self]);
[self _updateObjects:uniqueObjects dataSource:dataSource];
}
}
IGListKit
不支持 Object
间有相同的 diffIdentifier
,所以需要进行过滤,使用 NSMapTable
来进行记录,以 diffIdentifier
为 key
进行记录,如果 identifierMap
有记录,则不添加到 uniqueObjects
中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable>> *objects) {
if (objects == nil) {
return nil;
}
NSMapTable *identifierMap = [NSMapTable strongToStrongObjectsMapTable];
NSMutableArray *uniqueObjects = [NSMutableArray new];
for (id<IGListDiffable> object in objects) {
id diffIdentifier = [object diffIdentifier];
id previousObject = [identifierMap objectForKey:diffIdentifier];
if (diffIdentifier != nil
&& previousObject == nil) {
[identifierMap setObject:object forKey:diffIdentifier];
[uniqueObjects addObject:object];
} else {
IGLKLog(@"Duplicate identifier %@ for object %@ with object %@", diffIdentifier, object, previousObject);
}
}
return uniqueObjects;
}
去除重复的 Model
后,再调用 _updateObjects: dataSource:
获取对应的 IGListSectionController
,并将 IGListSectionController
和 Object
串联起来:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
- (void)_updateObjects:(NSArray *)objects dataSource:(id<IGListAdapterDataSource>)dataSource {
// 1. 状态标记,防止在更新数据源过程中刷新 `collectionView`
_isInObjectUpdateTransaction = YES;
// 2. 更新数据源过程中所需要用到的数据组合
NSMutableArray<IGListSectionController *> *sectionControllers = [NSMutableArray new];
NSMutableArray *validObjects = [NSMutableArray new];
IGListSectionMap *map = self.sectionMap;
NSMutableSet *updatedObjects = [NSMutableSet new];
// 3. 把当前的 `viewController` 和 `adapter` 存储到 `local thread dictionary` 中,以便在初始化 `IGListSectionController` 时使用
IGListSectionControllerPushThread(self.viewController, self);
for (id object in objects) {
// 4. 从 `map` 中获取对应的 `IGListSectionController` ,如果没有,则从 `dataSource` 生成的新的
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;
}
// 5. 设置 `sectionController` 的 `collectionContext` 和 `viewController` ,
// 防止 `sectioncontroller` 不是在 `-listAdapter:sectionControllerForObject:` 方法中创建的,
// 导致 `collectionContext` 和 `viewController` 没有更新
sectionController.collectionContext = self;
sectionController.viewController = self.viewController;
// 6. 如果找不到 `oldSection` ,则表示 `object` 是新增加的。如果新旧 `object` 不相等,则说明 `object` 有更新
const NSInteger oldSection = [map sectionForObject:object];
if (oldSection == NSNotFound || [map objectForSection:oldSection] != object) {
[updatedObjects addObject:object];
}
[sectionControllers addObject:sectionController];
[validObjects addObject:object];
}
// 7. 清除 `local thread dictionary` 的数据
IGListSectionControllerPopThread();
// 9. 更新 `validObjects` 和 `sectionControllers` 的绑定关系
[map updateWithObjects:validObjects sectionControllers:sectionControllers];
// 10. 所有 `sectionControllers` 都已经加载完成,进行 `object` 更新工作
for (id object in updatedObjects) {
[[map sectionControllerForObject:object] didUpdateToObject:object];
}
[self _updateBackgroundViewShouldHide:![self _itemCountIsZero]];
_isInObjectUpdateTransaction = NO;
}
至此,使用 IGListKit
的初始化流程已完成。
更新数据
IGListKit
在数据更新时刷新界面的流程和普通的 UICollectionView
使用方式类似,首先是根据用户操作/网络请求等对数据进行调整,然后调用 reloadData/performUpdates
刷新 UICollectionView
,但是与系统的 UICollectionView
不同,我们不再需要手动去计算哪些 Cell
进行了刷新/删除/插入/移动和进行相关操作, IGListKit
会自动帮我们完成这件事,我们所需要做的只是更新数据,然后调用 IGListAdapter
的对应方法即可。 而 IGListAdapter
提供了三种刷新方式,下面具体展开说说。
performUpdatesAnimated:completion:
performUpdatesAnimated:completion:
,等价于 UICollectionView
的 performBatchUpdates:completion:
方法,当数据源更新后,可以调用这个方法来进行局部刷新, IGListAdapter
内部会计算出新增/删除/更新的 Object
所对应的 Section
和位置,调用 UICollectionView
对应的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
- (void)performUpdatesAnimated:(BOOL)animated completion:(IGListUpdaterCompletion)completion {
id<IGListAdapterDataSource> dataSource = self.dataSource;
UICollectionView *collectionView = self.collectionView;
// 1. 如果 `dataSource` 或者 `collectionView` 为 `nil` ,直接返回,调用 `completion(NO)`
if (dataSource == nil || collectionView == nil) {
if (completion) {
completion(NO);
}
return;
}
// 2. 获取旧的 `objects` ,定义如何获取新的 `objects` 的 `block` ,
// 延迟执行 `dataSource` 的 `objectsForListAdapter:` 方法,等到需要时再执行,保证获取到的 `objects` 是最新的
NSArray *fromObjects = self.sectionMap.objects;
__weak __typeof__(self) weakSelf = self;
IGListToObjectBlock toObjectsBlock = ^NSArray *{
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return nil;
}
return [dataSource objectsForListAdapter:strongSelf];
};
// 3. 这里在局部刷新布局信息时会用到,标记一下进入局部刷新流程
[self _enterBatchUpdates];
// 4. 调用 `id <IGListUpdatingDelegate> updater` 对应的方法更新 `collectionView` ,
// 通过 `_collectionViewBlock ` 来获取 `collectionView` 延迟到真正更新时才执行 block,确保获取到的 `collectionView` 是正确的
[self.updater performUpdateWithCollectionViewBlock:[self _collectionViewBlock]
fromObjects:fromObjects
toObjectsBlock:toObjectsBlock
animated:animated
objectTransitionBlock:^(NSArray *toObjects) {
// 5. 这里的 `toObjects` 是由 `update` 进行计算后得出的新的数据源,设置 `previousSectionMap` ,更新数据源
weakSelf.previousSectionMap = [weakSelf.sectionMap copy];
[weakSelf _updateObjects:toObjects dataSource:dataSource];
} completion:^(BOOL finished) {
// 6. 完成刷新,复原标记
weakSelf.previousSectionMap = nil;
[weakSelf _notifyDidUpdate:IGListAdapterUpdateTypePerformUpdates animated:animated];
if (completion) {
completion(finished);
}
[weakSelf _exitBatchUpdates];
}];
}
reloadDataWithCompletion:
reloadDataWithCompletion:
全局刷新,作用跟 UICollectionView
的 reloadData
方法类似,会移除掉所有旧的 objects
和 IGListSectionController
,需要注意的是会重新生成所有 IGListSectionController
,所以是个有可能非常耗时的操作,在调用这个方法前必须清楚知道这一前提,一般情况下推荐使用 performUpdatesAnimated:completion:
来进行刷新。reloadData
的实现比 performUpdates
的要简单很多,只需要调用 update
对应的方法,清空 sectionMap
,更新 objects
即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion {
id<IGListAdapterDataSource> dataSource = self.dataSource;
UICollectionView *collectionView = self.collectionView;
if (dataSource == nil || collectionView == nil) {
if (completion) {
completion(NO);
}
return;
}
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);
}
}];
}
reloadObjects:
reloadObjects:
刷新 objects
所对应的 section
,在 object
有更新时进行调用,可以直接更新所对应的 sections
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)reloadObjects:(NSArray *)objects {
NSMutableIndexSet *sections = [NSMutableIndexSet new];
// 1. 使用 `_sectionMapUsingPreviousIfInUpdateBlock` 获取 `sectionMap` ,
// 因为 `reloadObjects` 是有可能在 `batch update` 过程中调用,如果是在 `batch update` 则使用旧的 `sectionMap`
IGListSectionMap *map = [self _sectionMapUsingPreviousIfInUpdateBlock:YES];
for (id object in objects) {
// 2. 根据 `object` 找到 `section` ,如果找不到则直接跳过
const NSInteger section = [map sectionForObject:object];
const BOOL notFound = section == NSNotFound;
if (notFound) {
continue;
}
[sections addIndex:section];
// 3. 根据 `section` 找一下 `object` ,如果新旧 `object` 不相等,`map` 则更新 `object` ,
// 同时更新 `sectionController` 的 `object`
if (object != [map objectForSection:section]) {
[map updateObject:object];
[[map sectionControllerForSection:section] didUpdateToObject:object];
}
}
UICollectionView *collectionView = self.collectionView;
[self.updater reloadCollectionView:collectionView sections:sections];
}
协议
IGListKit
围绕 IGListAdapter
定义了了大量协议,提供了良好的扩展性和接口封装。下面逐一来进行分析。
IGListAdapterDelegate
当 object
在屏幕上出现/消失,会调用 IGListKit
的 id <IGListAdapterDelegate> delegate
的相关方法,但是由于是基于 object
的,所以粒度没有办法精确到每个 Cell
,所以需要 Cell
级别的粒度,可以使用 IGListSectionController
的 id <IGListDisplayDelegate> displayDelegate
。
1
2
3
4
5
6
7
8
NS_SWIFT_NAME(ListAdapterDelegate)
@protocol IGListAdapterDelegate <NSObject>
- (void)listAdapter:(IGListAdapter *)listAdapter willDisplayObject:(id)object atIndex:(NSInteger)index;
- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingObject:(id)object atIndex:(NSInteger)index;
@end
UICollectionViewDelegate & UIScrollViewDelegate
这两个需要放在一起说说,因为 IGListKit
对 IGListAdapter
的这两个 delegate
做了处理:
1
2
3
4
5
6
// 只接收 `UICollectionViewDelegate` 的回调,不接收 `UIScrollViewDelegate` 的回调
@property (nonatomic, nullable, weak) id <UICollectionViewDelegate> collectionViewDelegate;
// 只接收 `UIScrollViewDelegate ` 的回调
@property (nonatomic, nullable, weak) id <UIScrollViewDelegate> scrollViewDelegate;
因为 UICollectionViewDelegate
是继承自 UIScrollViewDelegate
,所以 id <UICollectionViewDelegate> collectionViewDelegate
也会接收到 UIScrollViewDelegate
的回调,所以 IGListKit
使用一个 NSProxy
子类 IGListAdapterProxy
来对不同 delegate
的回调进行区分:
1
2
3
4
5
6
7
8
9
10
11
12
- (void)_createProxyAndUpdateCollectionViewDelegate {
_collectionView.delegate = nil;
self.delegateProxy = [[IGListAdapterProxy alloc] initWithCollectionViewTarget:_collectionViewDelegate
scrollViewTarget:_scrollViewDelegate
interceptor:self];
[self _updateCollectionViewDelegate];
}
- (void)_updateCollectionViewDelegate {
_collectionView.delegate = (id<UICollectionViewDelegate>)self.delegateProxy ?: self;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (instancetype)initWithCollectionViewTarget:(nullable id<UICollectionViewDelegate>)collectionViewTarget
scrollViewTarget:(nullable id<UIScrollViewDelegate>)scrollViewTarget
interceptor:(IGListAdapter *)interceptor {
IGParameterAssert(interceptor != nil);
if (self) {
_collectionViewTarget = collectionViewTarget;
_scrollViewTarget = scrollViewTarget;
_interceptor = interceptor;
}
return self;
}
- (BOOL)respondsToSelector:(SEL)aSelector {
// 先判断是否经过 `IGListAdapter` 进行处理,然后再判断 `_collectionViewTarget` 或者 `_scrollViewTarget` 是否可以响应
return isInterceptedSelector(aSelector)
|| [_collectionViewTarget respondsToSelector:aSelector]
|| [_scrollViewTarget respondsToSelector:aSelector];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
// 先判断 _interceptor 是否可以处理,如果可以,就转发给 _interceptor
if (isInterceptedSelector(aSelector)) {
return _interceptor;
}
// 因为 `UICollectionViewDelegate` 是 `UIScrollViewDelegate` 的子类,所以先检查 `_scrollViewTarget` 是否可以响应,
// 否则使用 _collectionViewTarget
return [_scrollViewTarget respondsToSelector:aSelector] ? _scrollViewTarget : _collectionViewTarget;
}
IGListAdapterPerformanceDelegate
id <IGListAdapterPerformanceDelegate> performanceDelegate
是为调用方提供了 UICollectionView
在滑动时会调用的方法耗时的回调,比如说监听获取 Cell
的耗时。
1
2
// 性能相关的 delegate ,
@property (nonatomic, nullable, weak) id <IGListAdapterPerformanceDelegate> performanceDelegate;
1
2
3
4
5
6
7
8
9
10
11
12
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
id<IGListAdapterPerformanceDelegate> performanceDelegate = self.performanceDelegate;
[performanceDelegate listAdapterWillCallDequeueCell:self];
IGListSectionController *sectionController = [self sectionControllerForSection:indexPath.section];
UICollectionViewCell *cell = [sectionController cellForItemAtIndex:indexPath.item];
/// ....
[performanceDelegate listAdapter:self didCallDequeueCell:cell onSectionController:sectionController atIndex:indexPath.item];
return cell;
}
这里去除其它代码,只保留 performanceDelegate
部分,可以看到在 cellForItemAtIndexPath
的开头和结尾调用了 performanceDelegate
的方法。
IGListBatchContext
支持 IGListBatchContext
协议的对象为 IGListSectionController
提供了 reload/insert/delete/move
等方法,在 IGListKit
中,这个对象是 IGListAdapter
,只是 IGListSectionController
不知道这个对象的具体类型,只知道是 id <IGListBatchContext>
。
-reloadInSectionController:atIndexes:
方法负责重新加载 IGListSectionController
中 indexes
所对应的 Cell
。 UICollectionView
并不支持在 batch updates
中 -reloadSections
或者 -reloadItemsAtIndexPaths:
,内部实现为通过 delete
和 insert
操作实现,这块的实现在某些操作下可能会导致异常。 假设有个 object
, 对应的 section
为 2 ,items
数量为 4 ,如果需要对 index
为 1 的 item
进行 reload
,先要创建一个 NSIndexPath
,item
为 1, section
为 2 ,当执行 -performBatchUpdates:
时, UICollectionView
会删除和插入这个 NSIndexPath
。如果这时我们在 position
为 2 中插入了一个 section
,原有的 seciton 2
就会变成 section 3
。然而,插入的 indexPath
的 section
还是 2 。那么 UICollectionView
就会在 section: 2 item: 1
执行一个插入动画,这时候就会抛出一个异常。 为了避免这个问题, IGListAdapter
会根据 sectionController
的新旧来获取不同的 NSIndexPath
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)reloadInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes {
UICollectionView *collectionView = self.collectionView;
if (indexes.count == 0) {
return;
}
[indexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) {
NSIndexPath *fromIndexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:YES];
NSIndexPath *toIndexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO];
if (fromIndexPath != nil && toIndexPath != nil) {
[self.updater reloadItemInCollectionView:collectionView fromIndexPath:fromIndexPath toIndexPath:toIndexPath];
}
}];
}
可以看到 fromIndexPath
是根据旧的数据源获取, toIndexPath
是新的数据源,同时还需要进行是否为 nil
的检查,因为 sectionController
有可能在批量更新中被删除了。
-invalidateLayoutInSectionController:atIndexes:
让 IGListSectionController
指定 Cell
布局信息失效:
1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)invalidateLayoutInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes {
UICollectionView *collectionView = self.collectionView;
if (indexes.count == 0) {
return;
}
NSArray *indexPaths = [self indexPathsFromSectionController:sectionController indexes:indexes usePreviousIfInUpdateBlock:NO];
UICollectionViewLayout *layout = collectionView.collectionViewLayout;
UICollectionViewLayoutInvalidationContext *context = [[[layout.class invalidationContextClass] alloc] init];
[context invalidateItemsAtIndexPaths:indexPaths];
[layout invalidateLayoutWithContext:context];
}
获取到 indexPaths
为新的 objects
所对应的 indexPaths
,这里是通过 [layout.class invalidationContextClass]
,因为 layout
有可能使用的是自定义的 UICollectionViewLayoutInvalidationContext
子类。
IGListCollectionContext
IGListCollectionContext
为 IGListSectionController
提供了 UICollectionView
的相关信息,如大小,复用,插入,删除,重新加载等。通过协议的方式可以把接口统一起来, IGListSectionController
只能使用 IGListCollectionContext
提供的接口,它不知道也不需要知道 IGListAdapter
的存在。
1
2
3
4
5
6
7
8
9
10
11
12
13
- (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass
withReuseIdentifier:(NSString *)reuseIdentifier
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index {
UICollectionView *collectionView = self.collectionView;
NSString *identifier = IGListReusableViewIdentifier(cellClass, nil, reuseIdentifier);
NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO];
if (![self.registeredCellIdentifiers containsObject:identifier]) {
[self.registeredCellIdentifiers addObject:identifier];
[collectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
}
return [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
}
在复用 Cell
时, IGListKit
不需要先调用 register
方法来注册 Cell
, IGListAdapter
内部记录了已经注册过的 Cell
,如果没有注册过,就先调用 UICollectionView
的 register
方法来进行注册,然后再调用 dequeueReusableCell
方法来从复用池中获取 Cell
。这是一个非常舒服的特性,如果每次使用都需要注册 Cell
,那么当业务变得非常复杂时,可能需要注册大量的 Cell
,会出现一个屏幕都无法完全显示注册 Cell
的方法。同时 UICollectionView
或者 UIViewController
也不需要和 Cell
进行交互,当 Cell
需要调整时,我们只需要在 IGListSectionController
中进行处理,或者直接替换对应的 IGListSectionController
。 上面说到在获取 Cell
时, IGListKit
会自动帮我们判断是否需要注册对应的 Cell
,下面来看下具体是如何实现的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NS_INLINE NSString *IGListReusableViewIdentifier(Class viewClass, NSString * _Nullable kind, NSString * _Nullable givenReuseIdentifier) {
return [NSString stringWithFormat:@"%@%@%@", kind ?: @"", givenReuseIdentifier ?: @"", NSStringFromClass(viewClass)];
}
- (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass
withReuseIdentifier:(NSString *)reuseIdentifier
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index {
UICollectionView *collectionView = self.collectionView;
NSString *identifier = IGListReusableViewIdentifier(cellClass, nil, reuseIdentifier);
NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO];
if (![self.registeredCellIdentifiers containsObject:identifier]) {
[self.registeredCellIdentifiers addObject:identifier];
[collectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
}
return [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
}
这个方法是在 IGListAdapter
内, IGListAdapter
会使用 NSMutableSet
来记录所有注册过的 Cell
对应的 reuseIdentifier
,在调用 dequeueReusableCellWithReuseIdentifier:forIndexPath:
前会先判断 registeredCellIdentifiers
是否有包含这个 identifier
,如果没有则进行注册,而 IGListReusableViewIdentifier
会采用 kind
, givenReuseIdentifier
和 viewClass
进行拼接的方式,生成新的 identifier
,也就不可能存在相同的 identifier
。
如果需要在 IGListSectionController
内部对数据源进行修改和刷新视图, IGListCollectionContext
也提供了如下方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)performBatchAnimated:(BOOL)animated updates:(void (^)(id<IGListBatchContext>))updates completion:(void (^)(BOOL))completion {
[self _enterBatchUpdates];
__weak __typeof__(self) weakSelf = self;
[self.updater performUpdateWithCollectionViewBlock:[self _collectionViewBlock] animated:animated itemUpdates:^{
// 更新 `isInUpdateBlock` ,执行 `block`
weakSelf.isInUpdateBlock = YES;
updates(weakSelf);
weakSelf.isInUpdateBlock = NO;
} completion: ^(BOOL finished) {
// 判断是否需要显示 emptyView
[weakSelf _updateBackgroundViewShouldHide:![weakSelf _itemCountIsZero]];
[weakSelf _notifyDidUpdate:IGListAdapterUpdateTypeItemUpdates animated:animated];
if (completion) {
completion(finished);
}
[weakSelf _exitBatchUpdates];
}];
}
-performBatchAnimated:updates:completion:
支持在执行多个 Cell
的相关操作。在 updates block
中更新 sectionController
的 dataSource
,然后调用 IGListBatchContext
的方法来插入/删除 items
:
1
2
3
4
5
6
7
8
9
10
11
// self 为 IGListSectionController
[self.collectionContext performBatchItemUpdates:^ (id<IGListBatchContext> batchContext>){
[self.items addObject:newItem];
[self.items removeObjectAtIndex:0];
NSIndexSet *inserts = [NSIndexSet indexSetWithIndex:[self.items count] - 1];
[batchContext insertInSectionController:self atIndexes:inserts];
NSIndexSet *deletes = [NSIndexSet indexSetWithIndex:0];
[batchContext deleteInSectionController:self atIndexes:deletes];
} completion:nil];
IGListAdapterUpdateListener
IGListKit
支持上面提到的几种刷新方式, IGListAdapterUpdateListener
则提供了相关回调:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef NS_ENUM(NSInteger, IGListAdapterUpdateType) {
// 调用 `-[IGListAdapter performUpdatesAnimated:completion:]`
IGListAdapterUpdateTypePerformUpdates,
// 调用 `-[IGListAdapter reloadDataWithCompletion:]`
IGListAdapterUpdateTypeReloadData,
// 在 `IGListSectionController` 中调用 `-[IGListCollectionContext performBatchAnimated:updates:completion:]`
IGListAdapterUpdateTypeItemUpdates,
};
@protocol IGListAdapterUpdateListener <NSObject>
- (void)listAdapter:(IGListAdapter *)listAdapter
didFinishUpdate:(IGListAdapterUpdateType)update
animated:(BOOL)animated;
@end
这个方法会在以下几种情况下调用:
- 执行
-[IGListAdapter performUpdatesAnimated:completion:]
的completion block
前调用; - 执行
-[IGListAdapter reloadDataWithCompletion:]
后调用; IGListSectionController
执行-[IGListCollectionContext performBatchAnimated:updates:completion:]
方法后。
IGListAdapter
支持设置多个 Listener
,对外提供了两个方法来添加和移除 Listener
:
1
2
3
- (void)addUpdateListener:(id<IGListAdapterUpdateListener>)updateListener;
- (void)removeUpdateListener:(id<IGListAdapterUpdateListener>)updateListener;
总结
IGListAdapter
作为 IGListKit
的适配器,对外提供相关的刷新接口和一些通用方法,对内负责管理 IGListSectionController
和 UICollectionView
,调用 dataSource
和 delegate
。
Comments powered by Disqus.