システムを継承してあまりにも強く押しています。他のジェネリックのサブクラスに基づくジェネリックスは、コンパイラの脳を壊す傾向があります。 (言った:あなたは絶対に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つのタイプに混ぜるように誘惑されているように思えます。まず、コードをどのように考えているかを考えてみましょう。 ListViewModel
はinit
コールによって制約されないため、型推論は使用できません。
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つも必要としません。単純な部分をクリックすると、複雑なソリューションを構築できます。
答えをありがとう!実際、 'GenericListViewModel'は私のアプリのほとんどのコントローラと同じように使用できます。しかし、viewModelは、モデルのフィルタリングや各コントローラ固有の状態の保持など、カスタムロジックを処理する必要がある場所があります。ですから、それをサブクラス化することは本当に便利です。 – marosoaie