开局

我们从 Cell 出发.

在很长一段时间里, 我都是将 Model 直接设置给 Cell, 然后重写 setModel: 在里面进行各种判断来设置 Cell 内容, 代码片段如下:

@interface Model
// ...
// ...
@end
@interface Cell
// ...
// ...
@property (nonatomic, strong, nullable) Model *model;
@end
@implementation Cell
// ...
// ...
- (void)setModel:(Model *)model {
    // ...
    _stateLabel.text = model.isLiked ? @"已点赞" : "点赞";
}
@end

像这里的 Model, 一般都是从网络请求或其他方式搞到的原始数据, 直接拿来使还不得劲, 需要根据业务来设置显示接口类型内容, 简单点的业务还好, 如果是复数组词杂的业务这里可能就变成了一堆判断了.

Level 1 DataSource

上面这个模式写的多了渐渐的会发现一些弊端: 复用 Cell 需要特定的 Model 才行, 勉强搞了对应 Model(可能还会给这个 Model 添加新的属性) 想要在 setModel: 中添加新的业务时, 总会担心影响旧的逻辑.

我琢磨着如数组初始化何解决这些问题. 从 Cell 的角度来看, 它需要的是一些显示数据以及将交互事件传递出来, 针对显示数据, 最简单的方式就是需要什么, 就提供什么, 请看以下代码片段:

@protocol DataSource
// ...
// ...
@property (nonatomic, copy, readonly) NSAttributedString *likingStateText;
@end
@protocol Delegate;
@interface Cell
@property (nonatomic, weak, nullable) id<DataSource> dataSource;
@property (nonatomic, weak, nullable) id<Delegate> delegate;
@end
@implementation Cell
- (void)setDataSource:(id<DataSource>)dataSource {
  // …
    // …
  _stateLabel.attributedText = dataSource.likingStateText;
}
@end

如上, 移除 Model 绑定, 添加 DataSource, 仅声明 Cell 需要的数据, 例如 Cell 需要一个富文本 likingStateText, 那我们就在 DataSource 中添加一个对应属性.

这样子数组排序 Cell 仅知道自己需Git要的数据git命令, 因此业务数组指针处理在 Cell 中就消失了, 转而移到了 DataSource 的实现中了.

Level 2 Item 模式

目前对任何实现了 DataSource 的类的对复杂度怎么计算的象都可以设置给 Cell, 所以针对不同的业务逻辑可以有多个 DataS接口ource 的实现, Cell 也因此得到了最大giti轮胎程度的复用.

以下是结合原初始化电脑的后果始数据以及业务逻辑来实现协议环路复杂度 DataSourc初始化电脑时出现问题未进行更改e 的代码片段:

@interface Model
// 原始数据
// ...
// ...
@end 
@protocol DataSource
// ...
// ...
@property (nonatomic, copy, readonly) NSAttributedString *likingStateText;
@end
// Item 结合原始数据, 实现了 DataSource
@interface Item<DataSource>
- (instancetype)initWithModel:(Model *)model;
@end
@implementation Item
- (instancetype)initWithModel:(Model *)model {
    // ... 部分业务逻辑
    // ...
    _likingStateText = [NSAttributedString.alloc initWithString:model.isLiked ? @"已点赞" : @"点赞"];
}
@end

数据流向如图:

iOS  对 Cell 的相关封装和复用

如上, Item 实现了协议 DataSource, 并在初始化时传入原始数据 Model 进数组词行了业务逻辑的处理.

通过时间复杂度对 DataSourc接口e 的不同实现, 使得业务的处理能够分散给不同的 Item. 相对数组和链表的区别于 Cell 来说, 它看到初始化电脑时出现问题的永远是 DataSource.

Level 3 Item 升级-注册信息

在我看来 Item 的出现是必然的, 它的能力也并不仅仅于此.

我们都知道 Cell 在使用之前需要先注册到 CollectionViewgithub永久回家地址 中, 而后还会涉及到 size 的计算.

我先拿注册 Cell 来说圈复杂度, 注册 Cell 无非就是用 Identifier 以及 Class 或 Nib 来注册, 而 Item 是最了解 Cell 的, 不如将接口文档注册信息做成 Item 的属性, 之后再找个时机读取这些软件复杂度信息进行注册, 对 Item 的扩展代码如下:

@interface Item<DataSource>
// ...
// ...
@property (nonatomic, readonly) Class cellClass; // cell 的类;
@property (nonatomic, readonly, nullable) UINib *cellNib; // 如果是xib, 就返回 nib;
@end
@implementation Item
- (Class)cellClass {
    return DemoCollectionViewCell.class;
}
- (UINib *_Nullable)cellNib {
    return nil;
}
@end

你可能会注意到好像漏了 Identifier 属性, 其实这个属性并不是必须的, 类名本身数组去重就是唯一的, NSStringFromClass(cellClass) 已经是最好的 Iden数组初始化tifier 了.

解决了github注册信息的问题, 先暂时抛开注册时机, 我们来看看 s数组i数组公式ze 的计算, 同样gitee由于 Item 对 Cell 了如指掌, 它一定知接口文档道如何计算合适的 size, 所以我们再次扩展 Item 如下:

@interface Item<DataSource>
// ...
// ...
- (CGSize)layoutSizeThatFits:(CGSize)size atIndexPath:(nullable NSIndexPath *)indexPath collectionViewScrollDirection:(UICollectionViewScrollDirection)scrollDirection;
@end

这个方法类似于 UIView.sizeThatFits:, Item 在这个方法里返回最适合的 size 用于 Cell 布局.初始化

Level 4 Item 升级-动态性

在此之前, 业务处理都是在 Item 初始化构复杂度建过程中初始化sdk什么意思完成的, 对数据的处理还缺少一些动态性初始化英文. 例如想要修改 Item 某个属性, 并及时的更初始化失败是怎么解决新 Cell, 这个时候该怎么办?

如果要做到及时更新, Item 就需要绑定一个 Cell 才行, 当 Item 属性被修改以后数组指针, 调用 Cell 相关方法刷新显示,数组初始化 下面是对 Item 的扩接口英文展代码:

@interface Cell
// ...
// ...
- (void)reloadLikingStatus;
@end
@interface Item<DataSource>
// ...
// ...
- (void)bindCell:(__kindof UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
- (void)unbindCell:(__kindof UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
@property (nonatomic, getter=isLiked) BOOL liked; // 想要变更的属性
@end
@implementation Item {
    __weak DemoCollectionViewCell *_cell;
}
// ...
// ...
- (void)bindCell:(Cell *)cell atIndexPath:(NSIndexPath *)indexPath {
    cell.dataSource = self;
    _cell = cell; // 绑定
}
- (void)unbindCell:(__kindof UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    _cell = nil; // 解绑
}
- (void)setLiked:(BOOL)isLiked {
    // ...
    // ...
    if ( _cell != nil ) [_cell reloadLikingStatus]; // 刷新 cell 显示
}
@end

如上, 在更新 Item 某个属性时, 通过重写相应的 set 方法及时的刷新数组去重方法 Cell.

当通过上述方案增加动态数组词性后, 随着数据内容的改变很有可能会使 size 也发初始化游戏启动器失败生改变. 而在实接口英文际的应用场景中, 我们为了避免重复计初始化是什么意思算 size, 一般会缓存每个 Item 的 size, 因此需要提供一种能够刷新缓存的机制, 标识出哪个 Item 需要刷新 size, 由于修改属性的动作 Item接口 是有感知的数组的定义(可以重写属性的 set 方法), 所以就初始化电脑的后果让 Item 自己来标识, 扩展代码如下:git教程

@interface Item<DataSource>
// ...
// ...
@property (nonatomic, readonly) BOOL needsLayout;
- (void)setNeedsLayout;
@end
@implementation Item
- (instancetype)initWith... {
    // ...
    // ...
    [self setNeedsLayout]; 
}
// 调用后代表需要刷新 size
- (void)setNeedsLayout {
    _needsLayout = YES;
}
- (CGSize)layoutSizeThatFits:(CGSize)size atIndexPath:(nullable NSIndexPath *)indexPath collectionViewScrollDirection:(UICollectionViewScrollDirection)scrollDirection {
    // ...
    // ...
    // 这个方法被调用, 就代表这里正在计算最新的 size; 
    // 由于已是最新 size, 此时可以将 _needsLayout 重置为 NO.
    _needsLayout = NO;
}
- (void)setLiked:(BOOL)isLiked {
    // ...
    // ...
    // 此处模拟内容发生改变后, size 也需要重新计算的场景;
    // 调用`setNeedsLayout`标识需要重新计算 size;
    [self setNeedsLayout];
}
@end

上面这个处理机制类似于 UIView.setNeedsLayout初始化电脑的后果CALayer.setNeedsLayout, 相当于做了个标接口自动化记到下次运行循环时执行更新操作.

Level 5 Item 升级-事件处理

现在我们再回到对 Cell 事件数组排序的处理上.

按之前旧模式的写法, 最终会在 VC 中为 Cell 设置 Delegate. 以下是旧模式的一些代码片段:

// 这是旧的模式, 供后续分析使用
// -------------- cell --------------
@protocol Delegate
// ...
// ...
- (void)likingItemWasTappedOnCell:(Cell *)cell; //
@end
@interface Cell
// ...
// ...
@property (nonatomic, weak, nullable) id<Delegate> delegate;
@end
// -------------- vc --------------
#import "Cell.h"
@interface ViewController<Delegate>
@end
@implementation ViewController
// ...
// ...
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
    // ...
    // ...
    // 设置 delegate
    if ( [cell isKindOfClass:Cell.class] ) [(Cell *)cell setDelegate:self]; 
}
// Cell - Delegate methods
- (void)likingItemWasTappedOnCell:(Cell *)cell {
    // ...
    // ...
}
@end

在这个模式中, VC 与 Ce数组和链表的区别ll 将会直接进行交互, 包括从导入头文件, 到注册, 再到设置模型和代理并实现代初始化sdk什么意思理方法以及在代理方法中针对业务上接口的一些处理和交互.

上面仅仅演示了一种 Cell 的情况, 实际开发中, 随着业务复杂度的上身, Cell 会越来越多, 在 VC 中进行维护会感觉越来越吃力, 修改可能会变得异常困难.

我们再次回到 Item 模式, 由前面所说的 Item 已经能够提供 Cell 注册时需要的信息以及替代设置模型的能力(并且拥有了动态性, 修改属性后可以刷新显示), 对了,初始化电脑 还有 size 计算的能力, 同时针对业务也会封装在 Item 内部. 那么剩下的 Cel初始化电脑的后果l.Deleg劳动复杂度ate 是否也可以通过 Itegiti轮胎m 进行设置?

我们从 Item 的角度看一下 DataSource 与 Delegate, DataSource 面向的是 Cell, 那 Delegate 面向的就接口是什么是 VC 了, 我们在 It接口测试em 层实现 DataSource, 那如果也实现 Delegate 呢, 将事件转初始化发给 VC 这条路实际上也行得通, 扩展 Item 的代码片段如下:

// -------------- DemoItem.h --------------
// 在这里做一层继承, 在此之前 Item 的相关能力可以按抽象类来设计的, 包含一些抽象方法
// DemoItem 将继承 Item 的能力, 作为 Item 的一个具体实现类
@interface DemoItem : Item
// ...
// ...
@property (nonatomic, copy, nullable) void(^likingHandler)(DemoItem *item);
@end
// -------------- DemoItem.m --------------
#import "Cell.h" // 在.m中导入cell头文件(cell 并不需要暴露在 item.h 中)
@interface DemoItem()<DataSource, Delegate>
// ...
// ...
@end
@implementation DemoItem 
// ... 
// ...
- (void)bindCell:(Cell *)cell atIndexPath:(NSIndexPath *)indexPath {
    cell.dataSource = self;
    cell.delegate = self; // 此处设置 cell.delegate
    _cell = cell; // 
}
// Cell - Delegate methods
- (void)likingItemWasTappedOnCell:(Cell *)cell {
    if ( _likingHandler != nil ) _likingHandler(self);
}
@end
// -------------- ViewController.m --------------
@implementation ViewController
// ...
// ...
- (void)_setupItems {
    // ...
    // ...
    NSArray<DemoItem *> *demoItems = ...;
    for ( DemoItem *item in demoItems ) {
        item.likingHandler = ^(DemoItem *item) {
            // ...
            // ...
            BOOL isLiked = !item.isLiked;
            // ... 在此进行网络请求后, 更新 item 属性
                item.liked = isLiked; 
        }
    }
}
@end

iOS  对 Cell 的相关封装和复用

如上 Item 内部进行 Cell.Delegate 的设置, 并将相应事件作为 block 属性对外提供. 在 VC 中, 通过设置Item.block 进行业务的处理.

到了这一步, 对 VC 来说它可能是感知不到 Cell 的存在, VC 仅与 Item 通过 block 进行事件的交互, 并在数组公式某个时机设置 Item 的属性或调用 Item 的方法, 间接的就能完成对 Cell 的刷新.

Cell圈复杂度 的代理事数组公式件我们处理完了, 到这里你可能还会想到 UICollectionView 的代理接口自动化方法中collectionView:didSelectItemAtIndexPath:该如何处理? 按照旧模式的写法这个方法里面会根据 indexPath 做一些判初始化磁盘断来处理点击事件, 同样堆满各种判断. 但把这个点击事件交给 Item 处理会是什么样的, 请看代码片段如下:

// -------------- Item.h --------------
@interface Item
// ...
// ...
@property (nonatomic, copy, nullable) void(^selectionHandler)(__kindof Item *item, NSIndexPath *indexPath);
@end
// -------------- ViewController.m --------------
@implementation ViewController
// ...
// ...
- (void)_setupItems {
    // ...
    // ...
    NSArray<DemoItem *> *demoItems = ...;
    for ( DemoItem *item in demoItems ) {
        // 设置点击处理
        item.selectionHandler = ^(DemoItem *item) {
            // ...
            // ...
        }
    }
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    // 获取对应位置的 item
    // itemAtIndexPath: 这个方法仅做模拟, 后续会介绍如何管理并获取 Item
    Item *item = [self itemAtIndexPath:indexPath];
    if ( item.selectionHandler != nil ) item.selectionHandler(item, indexPath);
}
@end

如上, Item 新增selectiogitinHandler, VC 中在合适位置进行配置, 最后在代理方法中进行调用.

L初始化电脑时出现问题evel 6 对 Item 进行管理-Section

Item 此时可以说是具备完备的功能了, 但是它仅代表单个个体, 我们将来github中文官网网页需要的是一组或更多组 Item, 需要有个类来负责进行管理(类似数组可以对元素提供 添加,软件复杂度 删除, 获取等的能力).

我们看 CollectionVgithub永久回家地址iew 的数据是由多个 Section 组成, 而 Section 由 SectionHeadgit教程er, Cell, SectionFooter 组成, 另外它们还都可以添加 Decoration.

管理 Item 自然就交给 S环路复杂度ection 来负责了, 代码片段如下:

@interface Section : NSObject
@property (nonatomic) CGFloat minimumInteritemSpacing;
@property (nonatomic) CGFloat minimumLineSpacing;
@property (nonatomic) UIEdgeInsets sectionInsets; // 内间距.
@property (nonatomic, strong, nullable) __kindof SectionHeaderFooter *header;
@property (nonatomic, strong, nullable) __kindof SectionHeaderFooter *footer;
// 对 item 进行管理
@property (nonatomic, readonly) NSInteger numberOfItems;
- (NSInteger)addItem:(Item *)item;
- (nullable NSIndexSet *)addItemsFromArray:(NSArray<Item *> *)items;
- (NSInteger)insertItem:(Item *)item atIndex:(NSInteger)index;
- (NSInteger)moveItem:(Item *)item toIndex:(NSInteger)index;
- (NSInteger)removeItemAtIndex:(NSInteger)index;
- (nullable NSIndexSet *)removeItemsInArray:(NSArray<Item *> *)items;
- (void)removeAllItems;
- (void)setNeedsLayout;
// ...
// ...
@end

这里说一下, 在数组去重方法上面的代码片段里, 不仅有 item, 还出现了 header, footer. 很显然, 不仅仅可以用 Item 封装 Cell, HeaderFooterView 这些视图也可以进行类似的封装.

Section 不展开细说了, 我们直接看定义的接口吧, 其中minimumInteritemSpacing, minimumLineSpacing, sectionInsets等是针对布局所做的接口测试对应属性, 还有针对header, footer, items进行增删改查等的管理.

这些代码片段不代表 Section 全部的能力, 实际应用中缺啥补啥就行.

Lgithubevel 7 对 Section 进行管理-CollectionProvidergithub

同样, Section 也算是单个个体, 我们需要的也是一组或更多组 Section, 也要有增删改查等的处理.

我们看代码片段如下:

@interface CollectionProvider : NSObject
@property (nonatomic, readonly) NSInteger numberOfSections;
- (void)addSectionWithBlock:(void(^NS_NOESCAPE)(__kindof Section *make))block;
- (NSInteger)addSection:(Section *)section;
- (nullable NSIndexSet *)addSections:(NSArray<Section *> *)sections;
- (NSInteger)insertSection:(Section *)section atIndex:(NSInteger)index;
- (void)replaceSectionAtIndex:(NSInteger)index withSection:(Section *)section;
- (nullable __kindof Section *)sectionAtIndex:(NSInteger)index;
- (nullable __kindof Item *)itemAtIndexPath:(NSIndexPath *)indexPath;
- (nullable NSIndexPath *)moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
- (nullable NSIndexPath *)insertItem:(Item *)item atIndexPath:(NSIndexPath *)indexPath;
- (NSInteger)removeSectionAtIndex:(NSInteger)index;
- (NSInteger)removeSection:(Section *)section;
- (nullable NSIndexSet *)removeSections:(NSArray<Section *> *)sections;
- (void)removeAllSections;
- (nullable NSIndexPath *)removeItemAtIndexPath:(NSIndexPath *)indexPath;
- (nullable NSIndexPath *)removeItem:(Item *)item;
- (nullable NSArray<NSIndexPath *> *)removeItemsInArray:(NSArray<Item *> *)items;
- (nullable NSArray<NSIndexPath *> *)removeItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
@end

Pro初始化是什么意思vider 能够对 Section 进行算法复杂度管理, 接口中对应的有增删改查等功能. 看看这些接口, 有没有感觉它就像是 CogithubllectionView 数据的提供方, 这也是环形复杂度起名 Provider 的原因.

通关-在 VC 中使用

到这里, 差不多可以通关了, 我们看一下数组初始化在 VC 中的使用效果, 代码如下:

@interface ViewController()<UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) CollectionProvider *provider;
@end
@implementation ViewController
// ...
// ...
- (void)_setupSections {
    // 这里仅添加一个Section, 实际开发中可能会添加很多的 Section
    [_provider addSectionWithBlock:^(__kindof Section * _Nonnull make) {
        make.minimumLineSpacing = 8;
    make.minimumInteritemSpacing = 8;
    make.sectionInsets = UIEdgeInsetsMake(12, 12, 12, 12);
    NSArray *models = nil; // 将要添加的数据
    for ( id model in models ) {
      DemoItem *item = [DemoItem.alloc initWithModel:model];
      item.selectionHandler = ^(__kindof Item * _Nonnull item, NSIndexPath * _Nonnull indexPath) {
                // ...
                // ...
      };
            item.likingHandler = ^(DemoItem *item) {
                // ...
                // ...
            }
      [make addItem:item];
    }
  }];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
  return _provider.numberOfSections;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
  return [_provider sectionAtIndex:section].numberOfItems;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
  [[_provider itemAtIndexPath:indexPath] bindCell:cell atIndexPath:indexPath];
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
  [[_provider itemAtIndexPath:indexPath] unbindCell:cell atIndexPath:indexPath];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
  Item *item = [_provider itemAtIndexPath:indexPath];
  if ( item.selectionHandler != nil ) item.selectionHandler(item, indexPath);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
  return [_provider sectionAtIndex:section].minimumLineSpacing;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
  return [_provider sectionAtIndex:section].minimumInteritemSpacing;
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
  return [_provider sectionAtIndex:section].contentInsets;
}
#pragma mark **-**
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  return nil; // 最后两坑--1
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
  return CGSizeZero; // 最后两坑--2
}
@end

哈哈, 最后还有两个坑没填呢.

我们先填一下collectionView:cellF复杂度怎么计算的orItemAtIndexPath:, 之前一直没说 Cell 注册的时机, 其实就是这里, 这个接口是什么地方就是我们注册的最佳时机. 这个复杂度方法需要返回 Cell接口是什么, 所以我们把注册和返回 Cell 的工作一起完成. 代码片段如下:

// -------------- Register.h --------------
@interface CollectionRegister : NSObject
// ...
// ...
// 根据 Item 的注册信息, 注册并返回 Cell
- (nullable __kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView 
                               dequeueReusableCellWithItem:(Item *)item forIndexPath:(NSIndexPath *)indexPath;
@end
// -------------- ViewController.m --------------
@interface ViewController()<UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
// ...
// ...
@property (nonatomic, strong) CollectionRegister *collectionRegister;
@end
@implementation ViewController
// ...
// ...
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    // 注册并返回 Cell
  return [_collectionRegister collectionView:collectionView dequeueReusableCellWithItem:[_provider itemAtIndexPath:indexPath] forIndexPath:indexPath]; 
}
@end

如上使用 CollectionRegister 进行注册和返回 Cell, 你可能会想是否有重复注册的问题, 哈初始化是什么意思哈, 这取决于你对这个方法的具体实现, 反正接口就那样了(甩锅).

接下来是最后一个坑collectionView:layout:sizeForItemAtIndexPath:, 通过初始化电脑的后果上面的做法你应该会想到我可能又要甩坑了, 代码如下:gitlab

// -------------- CollectionSizes.h --------------
@interface CollectionSizes : NSObject
// ...
// ...
- (CGSize)collectionView:(UICollectionView *)collectionView
         layout:(UICollectionViewLayout *)layout
      sizeForItem:(CollectionItem *)item
               inSection:(Section *)section
      atIndexPath:(NSIndexPath *)indexPath;
@end
// -------------- ViewController.m --------------
@interface ViewController()<UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
// ...
// ...
@property (nonatomic, strong) CollectionSizes *collectionSizes;
@end
@implementation ViewController
// ...
// ...
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return [_collectionSizes collectionView:collectionView layout:collectionViewLayout sizeForItem:[_provider itemAtIndexPath:indexPath] inSection:[_provider sectionAtIndex:indexPath.section] atIndexPath:indexPath];
}
@end

如上使用 CollectionSizes初始化失败是怎么解决 计算和返回 size, 你可能会想到是否有重复接口是什么计算的问题, 哈哈giticomfort是什么轮胎, 这取决于你对这个方法的具体实现, 反正接口就那样了(再次甩锅).

通关完毕.

留个 demo: g初始化电脑时出现问题ithub.com/changsanjia…gitee