类上的泛型参数约束会导致编译器崩溃 [英] Generic parameter constraint on class crashes the compiler
问题描述
这是一些swift3代码的简化形式:
class GenericListViewModel< CellViewModel> {
let cells:[CellViewModel]
必需init(cells:[CellViewModel]){
self.cells = cells
}
}
class ViewController< CellViewModel,ListViewModel:GenericListViewModel< CellViewModel>> {
var viewModel:ListViewModel
init(cellViewModels:[CellViewModel]){
viewModel = ListViewModel(cells:cellViewModels)
}
}
编译器崩溃时出现以下错误:
- 发布IR SIL功能@ _TFC4Xxxx14ViewControllercfT14cellViewModelsGSax__GS0_xq__代表'init'
at /.../ GenericStuff.swift:22:5
Am缺少某些东西,或者是Swift编译器错误?
修改:
我在此报告了 https://bugs.swift.org/browse/SR-3315 ,它看起来像是固定在当前的swift主分支中。
您的系统推送太难了继承。基于其他泛型的子类的泛型往往会打破编译器的大脑,而且通常不是真正意味着什么。 (说:编译器崩溃从来没有借口,所以你应该绝对打开一个 bugreport 。)
你真的想要子类化 GenericListViewModel
,然后参数化 ViewController
那个精确的子类?这似乎非常复杂,我没有看到你会如何得到任何实际的价值(因为你不能依赖任何额外的方法添加到你的子类,并且你已经有动态分派)。
你可能意味着有一个 CellViewModel
GenericListViewModel< CellViewModel>
所以,假设你不是真的意味着具体的参数化,让继承做它的工作。 ListViewModel
应为 typealias
,而不是类型参数:
class ViewController< CellViewModel> {
typealias ListViewModel = GenericListViewModel< CellViewModel>
var viewModel:ListViewModel
init(cellViewModels:[CellViewModel]){
viewModel = ListViewModel(cells:cellViewModels)
}
}
现在很好。也就是说,你真的需要视图模型作为参考类型吗?查看模型通常不需要身份本身(除非你使用KVO观察它们)。它们可以包装引用类型,但作为适配器,值类型通常很好。假设这是真的,那么这可以并且应该简化为结构:
struct GenericListViewModel< CellViewModel> {
let cells:[CellViewModel]
}
class ViewController< CellViewModel> {
typealias ListViewModel = GenericListViewModel< CellViewModel>
var viewModel:ListViewModel
init(cellViewModels:[CellViewModel]){
viewModel = ListViewModel(cells:cellViewModels)
}
}
您的目标是保持每个控制器特定的其他状态,我会非常小心使用子类。听起来你很想混合太多的功能到一个单一的类型。首先,想想你如何以你想象的方式调用你的代码。 ListViewModel
不受 init
调用的约束,因此不能使用类型推断。你必须像下面这样初始化:
let vc:ViewController< SomeCellModel,GenericListViewModelSubclass< SomeCellModel> = ViewController(cells:cells)
这很可怕,并且正在打击Swift想要帮助你的所有东西与。因为你想能够传递ListViewModel类型,我们只是传递它。这是协议,而不是类。
protocol CellViewModelProviding {
associatedtype CellViewModel
var cells:[CellViewModel] {get}
}
class ViewController< ListViewModel:CellViewModelProviding> {
var viewModel:ListViewModel
init(listViewModel:ListViewModel){
viewModel = listViewModel
}
}
现在我们可以创建不同的提供者了。
//为您的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 :[1,2,3],
谓词:{$ 0%2 == 0}))
$ b b这很不错,但我们可以做得更好。在正常情况下,我们不得不将我们的单元格包装在
AnyListViewModel
中。我们可能创建一个工厂方法来解决这个问题,但是哎。更好的答案是利用AnyListViewModel
的力量成为类型橡皮擦。这将是一个更高级的,所以如果你对上述解决方案感到满意,你可以停止,但让我们走过它,因为它是真正强大和灵活,如果你需要它。
首先,我们将
AnyListViewModel
转换为可以接受另一个视图列表模型或只是数组的完整类型橡皮擦。struct AnyListViewModel< CellViewModel> ;: CellViewModelProviding {
private let _cells:() [CellViewModel]
var cells:[CellViewModel] {return _cells()}
init(cells:[CellViewModel]){
_cells = {cells}
}
init< ListViewModel:CellViewModelProviding>(_ listViewModel:ListViewModel)
其中ListViewModel.CellViewModel == CellViewModel {
_cells = {listViewModel.cells}
}
}
现在
ViewController
必须关心什么样的ListViewModel
被传递。它可以将任何东西变成AnyListViewModel
并使用它。ViewController< CellViewModel> {
var viewModel:AnyListViewModel< CellViewModel>
init< ListViewModel:CellViewModelProviding>(listViewModel:ListViewModel)
其中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
//我们可以提取任何其他listViewModel
init< ListViewModel:CellViewModelProviding>(过滤listViewModel:ListViewModel,
withPredicate predicate:@escaping(CellViewModel) - &
其中ListViewModel.CellViewModel == CellViewModel {
self.listViewModel = AnyListViewModel(listViewModel)
self.predicate =谓词
}
//或者,为了方便,我们可以处理简单的[cell] case
init(过滤单元格:[CellViewModel],withPredicate predicate:@escaping(CellViewModel) - > Bool){
self。 init(过滤:AnyListViewModel(cells:cells),withPredicate:predicate)
}
}
$ b b这是事情变得强大的地方。我们说
FilteredListViewModel
可以采取一些单元格并过滤它们,当然。但它也可以过滤任何其他视图列表模型。let someList = AnyListViewModel(cells:[1,2,3])
let evenList = FilteredListViewModel someList,withPredicate:{$ 0%2 == 0})
你可以将过滤与排序或修改单元格或任何东西粘合在一起。你不需要一个超级专业的子类,做你需要的一切。您可以一起点击更简单的部分来构建复杂的解决方案。
This is a simplified form of some swift3 code:
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) } }
The compiler crashes with the following error:
- While emitting IR SIL function @_TFC4Xxxx14ViewControllercfT14cellViewModelsGSax__GS0_xq__ for 'init' at /.../GenericStuff.swift:22:5
Am is missing something, or is this a Swift compiler bug?
Edit:
I reported this here https://bugs.swift.org/browse/SR-3315 and it looks like it's fixed in current swift master branch.
解决方案You're pushing the system too hard with inheritance. Generics based on subclasses of other generics tends to break the compiler's brain, and is usually not really what you meant anyway. (That said: there is never an excuse for the compiler crashing, so you should absolutely open a bugreport.)
Do you really mean to subclass
GenericListViewModel
and then parameterizeViewController
on that precise subclass? This seems very over-complicated and I'm not seeing how you would get any actual value out of it (since you can't rely on any additional methods added to your subclasses, and you already have dynamic dispatch). You're using both subclasses and generics to solve the same problem.What you likely mean is that there's a
CellViewModel
and you wantGenericListViewModel<CellViewModel>
to wrap that, and aren't thinking about subclasses at all.So, assuming you don't really mean to parameterize this specifically, let inheritance do its job.
ListViewModel
should betypealias
, not a type parameter:class ViewController<CellViewModel> { typealias ListViewModel = GenericListViewModel<CellViewModel> var viewModel: ListViewModel init(cellViewModels: [CellViewModel]) { viewModel = ListViewModel(cells: cellViewModels) } }
Now it's fine. That said, do you really need the view model to be a reference type? View models often don't need identity themselves (unless you're observing them with KVO). They may wrap a reference type, but as an adapter, a value type is often fine. Assuming this is true for you, then this can and should be simplified to a struct:
struct GenericListViewModel<CellViewModel> { let cells: [CellViewModel] } class ViewController<CellViewModel> { typealias ListViewModel = GenericListViewModel<CellViewModel> var viewModel: ListViewModel init(cellViewModels: [CellViewModel]) { viewModel = ListViewModel(cells: cellViewModels) } }
To your goals of "custom logic like filtering the model, or keeping some other state specific to each controller," I would be very careful of using subclasses for this. It sounds like you're tempted to mix too much functionality into a single type. First, think about how you'd call your code the way you're thinking about it.
ListViewModel
isn't constrained by theinit
call, so you can't use type-inference. You'll have to initialize it like:let vc: ViewController<SomeCellModel, GenericListViewModelSubclass<SomeCellModel>> = ViewController(cells: cells)
That's pretty hideous and is fighting all the things Swift wants to help you with. Since you want to be able to pass in the ListViewModel type, let's just pass it in. This is what protocols are for, not classes.
protocol CellViewModelProviding { associatedtype CellViewModel var cells: [CellViewModel] { get } } class ViewController<ListViewModel: CellViewModelProviding> { var viewModel: ListViewModel init(listViewModel: ListViewModel) { viewModel = listViewModel } }
Now we can create different providers.
// 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 }
Now we can use it with:
let vc = ViewController(listViewModel: AnyListViewModel(cells: [1,2,3])) let vc2 = ViewController(listViewModel: FilteredListViewModel(unfilteredCells: [1,2,3], predicate: { $0 % 2 == 0 }))
So that's pretty nice, but we could do better. It's kind of annoying to have to wrap our cells up in an
AnyListViewModel
in the normal case. We could probably create a factory method to get around this, but yuck. The better answer is to make use of the power ofAnyListViewModel
to be a type eraser. This is going to get a little more advanced, so if you're happy with the above solution, you can stop, but let's walk through it because it's really powerful and flexible if you need it.First, we convert
AnyListViewModel
into a full type eraser that can accept either another view list model, or just an array.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 } } }
Now
ViewController
doesn't have to care what kind ofListViewModel
is passed. It can turn anything into anAnyListViewModel
and work with that.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) } }
OK, that's cool, but it's not a huge improvement. Well, let's rebuild
FilteredListViewModel
and see what that gets us.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) } }
This is where things get powerful. We've said
FilteredListViewModel
can take some cells and filter them, sure. But it can also filter any other view list model.let someList = AnyListViewModel(cells: [1,2,3]) let evenList = FilteredListViewModel(filtering: someList, withPredicate: { $0 % 2 == 0 })
So now you can chain things together. You could glue together filtering with sorting or something that modified the cells or whatever. You don't need one super-specialized subclass that does everything you need. You can click together simpler pieces to build complex solutions.
这篇关于类上的泛型参数约束会导致编译器崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!