Home IGListKit 的管理者 - IGListAdapter
Post
Cancel

IGListKit 的管理者 - IGListAdapter

初始化

IGListAdapter 负责处理 UICollectionViewDataSourceDelegate ,所有 DataSource/Delegate 的相关方法都会在 IGListAdapter 内部消化完毕,调用方只需要设置 IGListAdapterdataSourcecollectionView 即可, 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 的自定义 hashFunctionisEqualFunction ,由于 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 的中心调度器,负责串联起 IGListSectionControllerModelUICollectionViewUICollectionReusableView 之间的关系,在设置 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 来进行记录,以 diffIdentifierkey 进行记录,如果 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 ,并将 IGListSectionControllerObject 串联起来:

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: ,等价于 UICollectionViewperformBatchUpdates: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: 全局刷新,作用跟 UICollectionViewreloadData 方法类似,会移除掉所有旧的 objectsIGListSectionController ,需要注意的是会重新生成所有 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 在屏幕上出现/消失,会调用 IGListKitid <IGListAdapterDelegate> delegate 的相关方法,但是由于是基于 object 的,所以粒度没有办法精确到每个 Cell ,所以需要 Cell 级别的粒度,可以使用 IGListSectionControllerid <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

这两个需要放在一起说说,因为 IGListKitIGListAdapter 的这两个 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: 方法负责重新加载 IGListSectionControllerindexes 所对应的 CellUICollectionView 并不支持在 batch updates-reloadSections 或者 -reloadItemsAtIndexPaths: ,内部实现为通过 deleteinsert 操作实现,这块的实现在某些操作下可能会导致异常。 假设有个 object , 对应的 section 为 2 ,items 数量为 4 ,如果需要对 index 为 1 的 item 进行 reload ,先要创建一个 NSIndexPathitem 为 1, section 为 2 ,当执行 -performBatchUpdates: 时, UICollectionView 会删除和插入这个 NSIndexPath 。如果这时我们在 position 为 2 中插入了一个 section ,原有的 seciton 2 就会变成 section 3 。然而,插入的 indexPathsection 还是 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

IGListCollectionContextIGListSectionController 提供了 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 方法来注册 CellIGListAdapter 内部记录了已经注册过的 Cell ,如果没有注册过,就先调用 UICollectionViewregister 方法来进行注册,然后再调用 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 会采用 kindgivenReuseIdentifierviewClass 进行拼接的方式,生成新的 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 中更新 sectionControllerdataSource ,然后调用 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

这个方法会在以下几种情况下调用:

  1. 执行 -[IGListAdapter performUpdatesAnimated:completion:]completion block 前调用;
  2. 执行 -[IGListAdapter reloadDataWithCompletion:] 后调用;
  3. IGListSectionController 执行 -[IGListCollectionContext performBatchAnimated:updates:completion:] 方法后。

IGListAdapter 支持设置多个 Listener ,对外提供了两个方法来添加和移除 Listener :

1
2
3
- (void)addUpdateListener:(id<IGListAdapterUpdateListener>)updateListener;

- (void)removeUpdateListener:(id<IGListAdapterUpdateListener>)updateListener;

总结

IGListAdapter 作为 IGListKit 的适配器,对外提供相关的刷新接口和一些通用方法,对内负责管理 IGListSectionControllerUICollectionView ,调用 dataSourcedelegate

IGListAdapter

This post is licensed under CC BY 4.0 by the author.

IGListKit 的基石 - IGListSectionController

IGListKit 的数据处理 - Updater&Diff

Comments powered by Disqus.