swift - 类上的通用参数约束会使编译器崩溃

标签 swift crash

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

编译器因以下错误而崩溃:

  1. 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/

相关文章:

json - 使用 SwiftyJSON 读取本地 JSON 文件

android - 开始某些 Activity 时android程序崩溃

c - malloc 调用 realloc,然后崩溃

尝试添加文本字段时 iOS 8.3 UIAlertController 崩溃

ios - 如何在点击 tableViewCell 时将 UIImageView 置于前台?

swift - 删除元组数组中的重复值

ios - 如何在核心数据中设置与对象的关系?

ios - 生成实时录音机图

android - 按四个按钮中的任何一个时我的应用程序崩溃

c - 程序崩溃了,我不明白为什么