2016-11-29 16 views
1

をクラッシュこれは、いくつかのswift3コードの簡略化した形であるジェネリックパラメータの制約がコンパイラ

  1. While emitting IR SIL function @_TFC4Xxxx14ViewControllercfT14cellViewModelsGSax__GS0_xq__ for 'init' at /.../GenericStuff.swift:22:5

アムは何かが足りない、またはですこのSwiftコンパイラのバグ?

編集

私はhttps://bugs.swift.org/browse/SR-3315ここにこれを報告し、それが現在の迅速マスターブランチに固定しているように見えます。

答えて

3

システムを継承してあまりにも強く押しています。他のジェネリックのサブクラスに基づくジェネリックスは、コンパイラの脳を壊す傾向があります。 (言った:あなたは絶対にbugreportを開く必要がありますので、コンパイラのクラッシュのための言い訳は、決してありません)

あなたは本当にGenericListViewModelをサブクラス化し、その正確なサブクラスにViewControllerをパラメータ化することを意味しますか?これは非常に複雑すぎるようです。サブクラスに追加された追加のメソッドに頼ることができず、既に動的ディスパッチを使用しているため、実際の価値をどのように得るのか分かりません。あなたは、同じ問題を解決するためにサブクラスとジェネリックスの両方を使用しています。

おそらく、CellViewModelがあり、GenericListViewModel<CellViewModel>がそれを囲み、サブクラスについて全く考えていないことを意味します。

したがって、これを具体的にパラメータ化することを実際には意味しないと仮定して、継承はその仕事をします。

class ViewController<CellViewModel> { 
    typealias ListViewModel = GenericListViewModel<CellViewModel> 
    var viewModel: ListViewModel 

    init(cellViewModels: [CellViewModel]) { 
     viewModel = ListViewModel(cells: cellViewModels) 
    } 
} 

は今結構です:ListViewModelはないタイプのパラメータ、typealiasでなければなりません。つまり、参照モデルとしてビューモデルが本当に必要ですか?ビューモデルは、しばしばアイデンティティを必要としません(KVOで観察しない限り)。それらは参照型をラップするかもしれませんが、アダプタとして、値型はしばしば問題ありません。これはあなたのための真であると仮定すると、これはと構造体のように簡略化されなければならないことができます:カスタム・ロジック」のあなたの目標に

struct GenericListViewModel<CellViewModel> { 
    let cells: [CellViewModel] 
} 

class ViewController<CellViewModel> { 
    typealias ListViewModel = GenericListViewModel<CellViewModel> 
    var viewModel: ListViewModel 

    init(cellViewModels: [CellViewModel]) { 
     viewModel = ListViewModel(cells: cellViewModels) 
    } 
} 

モデルをフィルタリング、または各コントローラに固有のいくつかの他の状態を維持するように、 "私はこれにサブクラスを使用することに非常に注意しています。あまりにも多くの機能を1つのタイプに混ぜるように誘惑されているように思えます。まず、コードをどのように考えているかを考えてみましょう。 ListViewModelinitコールによって制約されないため、型推論は使用できません。

let vc: ViewController<SomeCellModel, GenericListViewModelSubclass<SomeCellModel>> = ViewController(cells: cells) 

スウィフトがあなたに手伝ってくれるすべてのものと戦っています。 ListViewModel型を渡すことができるようにしたいので、それを渡しましょう。これは、クラスではなく、プロトコルです。

protocol CellViewModelProviding { 
    associatedtype CellViewModel 
    var cells: [CellViewModel] { get } 
} 

class ViewController<ListViewModel: CellViewModelProviding> { 
    var viewModel: ListViewModel 

    init(listViewModel: ListViewModel) { 
     viewModel = listViewModel 
    } 
} 

これで、さまざまなプロバイダを作成できます。だから、それはかなりいいですが、私たちはより良い行うことができ

let vc = ViewController(listViewModel: AnyListViewModel(cells: [1,2,3])) 
let vc2 = ViewController(listViewModel: FilteredListViewModel(unfilteredCells: [1,2,3], 
                   predicate: { $0 % 2 == 0 })) 

// 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 
} 

今、私たちは、とそれを使用することができます。通常のケースではAnyListViewModelでセルを折り返さなければならないのは厄介なことです。おそらくこれを回避するためのファクトリメソッドを作成することができますが、yuck。より良い答えは、AnyListViewModelの力をtype eraserとすることです。これはもう少し高度なものになるでしょう。もしあなたが上記の解決策に満足すれば、あなたは止めることができますが、必要な場合には本当に強力で柔軟性があるので、歩きましょう。

まず、AnyListViewModelを、別のビューリストモデル、または配列だけを受け入れるフルタイプのイレーサーに変換します。

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がいくつかのセルを取ってそれらをフィルタリングできると言ってきました。しかし、他のビューリストモデルをフィルタリングすることもできます。

let someList = AnyListViewModel(cells: [1,2,3]) 
let evenList = FilteredListViewModel(filtering: someList, withPredicate: { $0 % 2 == 0 }) 

これで、物事を連鎖させることができます。並べ替えや細胞やその他のものを修正したものを一緒に糊付けすることができます。あなたは、必要なすべてを行うスーパー特化されたサブクラスを1つも必要としません。単純な部分をクリックすると、複雑なソリューションを構築できます。

+0

答えをありがとう!実際、 'GenericListViewModel'は私のアプリのほとんどのコントローラと同じように使用できます。しかし、viewModelは、モデルのフィルタリングや各コントローラ固有の状態の保持など、カスタムロジックを処理する必要がある場所があります。ですから、それをサブクラス化することは本当に便利です。 – marosoaie