类上的泛型参数约束会导致编译器崩溃 [英] Generic parameter constraint on class crashes the compiler

查看:169
本文介绍了类上的泛型参数约束会导致编译器崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一些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)
}
}

编译器崩溃时出现以下错误:



  1. 发布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:

  1. 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 parameterize ViewController 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 want GenericListViewModel<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 be typealias, 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 the init 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 of AnyListViewModel 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 of ListViewModel is passed. It can turn anything into an AnyListViewModel 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆