这是一些 swift3 代码的简化形式:
class GenericListViewModel<CellViewModel> {
let cells: [CellViewModel]
required init(cells: [CellViewModel]) {
self.cells = cells
}
}
class ViewController<CellViewModel, ListViewModel: GenericListViewModel<CellViewModel>> {
var viewModel: ListViewModel
init(cellViewModels: [CellViewModel]) {
viewModel = ListViewModel(cells: cellViewModels)
}
}
编译器因以下错误而崩溃:
- While emitting IR SIL function @_TFC4Xxxx14ViewControllercfT14cellViewModelsGSax__GS0_xq__ for 'init' at /.../GenericStuff.swift:22:5
我遗漏了什么,或者这是一个 Swift 编译器错误?
编辑:
我在这里报告了这个https://bugs.swift.org/browse/SR-3315看起来它已修复在当前的 swift master 分支中。
最佳答案
您对继承系统的要求太高了。基于其他泛型的子类的泛型往往会破坏编译器的大脑,而且通常并不是你真正想要的。 (也就是说:编译器崩溃永远没有借口,所以你绝对应该打开一个 bugreport 。)
你真的想继承GenericListViewModel
吗?然后参数化 ViewController
在那个精确的子类上?这看起来非常复杂,我看不到如何从中获得任何实际值(value)(因为您不能依赖添加到子类的任何其他方法,并且您已经有了动态调度)。您同时使用子类和泛型来解决同一个问题。
您的意思可能是有一个 CellViewModel
你想要GenericListViewModel<CellViewModel>
包装它,根本不考虑子类。
因此,假设您真的不想具体地参数化它,让继承来完成它的工作。 ListViewModel
应该是 typealias
,不是类型参数:
class ViewController<CellViewModel> {
typealias ListViewModel = GenericListViewModel<CellViewModel>
var viewModel: ListViewModel
init(cellViewModels: [CellViewModel]) {
viewModel = ListViewModel(cells: cellViewModels)
}
}
现在好了。也就是说,您真的需要将 View 模型作为引用类型吗? View 模型本身通常不需要标识(除非您使用 KVO 观察它们)。它们可能包装引用类型,但作为适配器,值类型通常就可以了。假设这对您来说是正确的,那么这可以而且应该简化为一个结构:
struct GenericListViewModel<CellViewModel> {
let cells: [CellViewModel]
}
class ViewController<CellViewModel> {
typealias ListViewModel = GenericListViewModel<CellViewModel>
var viewModel: ListViewModel
init(cellViewModels: [CellViewModel]) {
viewModel = ListViewModel(cells: cellViewModels)
}
}
为了您的目标“自定义逻辑,如过滤模型,或保持每个 Controller 特定的其他状态”,我会非常小心地为此使用子类。听起来您很想将太多功能混合到一个类型中。首先,考虑如何按照您的想法调用代码。 ListViewModel
不受 init
的约束调用,所以你不能使用类型推断。你必须像这样初始化它:
let vc: ViewController<SomeCellModel, GenericListViewModelSubclass<SomeCellModel>> = ViewController(cells: cells)
这太可怕了,它正在与 Swift 想要帮助你的所有事情作斗争。由于您希望能够传入 ListViewModel 类型,所以我们只传入它。这是协议(protocol)的用途,而不是类。
protocol CellViewModelProviding {
associatedtype CellViewModel
var cells: [CellViewModel] { get }
}
class ViewController<ListViewModel: CellViewModelProviding> {
var viewModel: ListViewModel
init(listViewModel: ListViewModel) {
viewModel = listViewModel
}
}
现在我们可以创建不同的提供者。
// A more standard name for your GenericListViewModel
struct AnyListViewModel<CellViewModel>: CellViewModelProviding {
let cells: [CellViewModel]
}
struct FilteredListViewModel<CellViewModel>: CellViewModelProviding {
var cells: [CellViewModel] {
return unfilteredCells.filter(predicate)
}
var unfilteredCells: [CellViewModel]
var predicate: (CellViewModel) -> Bool
}
现在我们可以将它用于:
let vc = ViewController(listViewModel: AnyListViewModel(cells: [1,2,3]))
let vc2 = ViewController(listViewModel: FilteredListViewModel(unfilteredCells: [1,2,3],
predicate: { $0 % 2 == 0 }))
这很好,但我们可以做得更好。不得不将我们的细胞包裹在 AnyListViewModel
中有点烦人在正常情况下。我们或许可以创建一个工厂方法来解决这个问题,但是糟糕。更好的答案是利用 AnyListViewModel
的力量成为type eraser .这将变得更高级一些,所以如果您对上述解决方案感到满意,您可以停下来,但让我们逐步了解它,因为如果您需要它,它确实非常强大和灵活。
首先,我们转换AnyListViewModel
变成一个完整类型的橡皮擦,它可以接受另一个 View 列表模型,或者只是一个数组。
struct AnyListViewModel<CellViewModel>: CellViewModelProviding {
private let _cells: () -> [CellViewModel]
var cells: [CellViewModel] { return _cells() }
init(cells: [CellViewModel]) {
_cells = { cells }
}
init<ListViewModel: CellViewModelProviding>(_ listViewModel: ListViewModel)
where ListViewModel.CellViewModel == CellViewModel {
_cells = { listViewModel.cells }
}
}
现在ViewController
不必关心什么样的ListViewModel
通过了。它可以将任何东西变成 AnyListViewModel
并与之合作。
class ViewController<CellViewModel> {
var viewModel: AnyListViewModel<CellViewModel>
init<ListViewModel: CellViewModelProviding>(listViewModel: ListViewModel)
where ListViewModel.CellViewModel == CellViewModel {
viewModel = AnyListViewModel(listViewModel)
}
init(cells: [CellViewModel]) {
viewModel = AnyListViewModel(cells: cells)
}
}
好的,这很酷,但这并不是一个巨大的改进。好吧,让我们重建FilteredListViewModel
看看我们得到了什么。
struct FilteredListViewModel<CellViewModel>: CellViewModelProviding {
var cells: [CellViewModel] {
return listViewModel.cells.filter(predicate)
}
private var listViewModel: AnyListViewModel<CellViewModel>
var predicate: (CellViewModel) -> Bool
// We can lift any other listViewModel
init<ListViewModel: CellViewModelProviding>(filtering listViewModel: ListViewModel,
withPredicate predicate: @escaping (CellViewModel) -> Bool)
where ListViewModel.CellViewModel == CellViewModel {
self.listViewModel = AnyListViewModel(listViewModel)
self.predicate = predicate
}
// Or, just for convenience, we can handle the simple [cell] case
init(filtering cells: [CellViewModel], withPredicate predicate: @escaping (CellViewModel) -> Bool) {
self.init(filtering: AnyListViewModel(cells: cells), withPredicate: predicate)
}
}
这就是事情变得强大的地方。我们说过 FilteredListViewModel
当然可以取一些细胞并过滤它们。但它也可以过滤任何其他 View 列表模型。
let someList = AnyListViewModel(cells: [1,2,3])
let evenList = FilteredListViewModel(filtering: someList, withPredicate: { $0 % 2 == 0 })
所以现在您可以将事物链接在一起。您可以将过滤与排序或修改单元格或其他内容的内容粘合在一起。你不需要一个 super 专业的子类来完成你需要的一切。您可以将较简单的部分组合在一起以构建复杂的解决方案。
关于swift - 类上的通用参数约束会使编译器崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40860188/