Swift使用泛型基类时子类UICollectionView代理方法不触发
问题成因
这是Swift泛型类与Objective-C运行时交互的已知限制:
- UIKit 所有代理协议(包括
UICollectionViewDelegate)都是从Objective-C导出的,代理方法的调用完全依赖OC运行时的消息派发机制。 - 当非泛型类遵循OC协议时,Swift运行时会在类加载阶段完整扫描当前类和所有子类的方法列表,把实现的协议方法注册到OC派发链路中,因此非泛型版本的代码可以正常触发子类的代理回调。
- 当父类是泛型类型时,为了支持泛型特化、提升运行性能,Swift不会为泛型类的子类动态生成完整的OC方法派发表,OC运行时查找代理方法时只能遍历到泛型父类的方法列表,无法感知子类中实现的可选协议方法。由于泛型基类没有实现
collectionView(_:didSelectItemAt:)方法,UIKit查找不到对应的方法实现,自然就不会触发回调。
修复方案
按实现成本和稳定性排序,有以下三种可行方案:
方案1:子类显式声明遵循协议,代理方法加override关键字(推荐)
这是侵入性最低、兼容性最好的修复方式,只需要修改子类代码:
- 在子类的继承声明后,显式标注遵循
UICollectionViewDelegate协议(即使父类已经遵循该协议,重复声明不会产生冲突) - 实现代理方法时添加
override关键字,强制编译器将该方法标记为可被OC运行时感知的重写方法
修改后的子类代码如下:
class ViewController: SimpleListViewController<ViewControllerViewModel>, UICollectionViewDelegate { override init(model: ViewControllerViewModel = ViewControllerViewModel()) { super.init(model: model) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "List" view.backgroundColor = .red configureHierarchy() model.configureDataSource(using: collectionView) model.applySnapshot() model.foo() } override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { print("Here") } }
该方案不需要修改基类逻辑,也不会影响现有泛型的灵活性,是生产环境的首选方案。
方案2:抽离基类的非泛型层
如果不想在每个子类都重复声明协议遵循,可以在泛型基类和UIViewController之间加一层非泛型的中间基类,让中间基类遵循代理协议、实现基础的通用逻辑,泛型基类继承自这个中间基类,就可以避开泛型类的OC方法扫描问题。
方案3:基类实现默认代理方法做转发
在泛型基类中实现所有需要用到的代理方法,提供默认空实现,子类重写时就可以被正常派发。但这种方式需要在基类补全所有用到的代理方法,冗余度高,仅适合代理方法数量少的场景。
内容的提问来源于stack exchange,提问作者Richard Witherspoon
相关产品推荐
相关产品推荐

