2016-11-29 26 views
1

這是一些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

AM是失去了一些東西,或者是這是一個Swift編譯器錯誤?

編輯

我報這個在這裏https://bugs.swift.org/browse/SR-3315,它看起來像它的固定電流SWIFT主分支。

回答

3

你在繼承中推動系統太難了。基於其他泛型的子類的泛型往往會打破編譯器的大腦,而且通常並不是真正意義上的。 (也就是說:從來就沒有編譯器崩潰的藉口,那麼你絕對應該打開bugreport。)

你真的要繼承GenericListViewModel,然後對精確子類參數ViewController?這看起來非常複雜,我不知道如何從中獲得任何實際價值(因爲您不能依賴添加到您的子類中的任何其他方法,並且您已經有動態分派)。您正在使用子類和泛型來解決相同的問題。

你可能的意思是,有一個CellViewModel,你想GenericListViewModel<CellViewModel>包裝,並沒有考慮子類。

所以,假設你並不是真的想要具體參數化,讓繼承完成它的工作。 ListViewModeltypealias,而不是一個類型參數:

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) 

這是相當可怕的,並反對一切斯威夫特想要幫助你的東西。由於您希望能夠傳入ListViewModel類型,因此我們只需將它傳入。這是協議的用途,而不是類。

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成一個完整的類型橡皮擦用來可以接受另一視圖列表模式,或者只是一個數組。

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

所以,現在你可以鏈接在一起。你可以粘貼在一起過濾排序或修改單元格或任何東西。你不需要一個超級特殊的子類來完成你所需要的一切。您可以點擊更簡單的部分來構建複雜的解決方案。

+0

感謝您的回答!事實上,'GenericListViewModel'可以用在我的應用程序的大多數控制器中。但有些地方viewModel必須處理一些自定義邏輯,比如過濾模型,或者爲每個控制器保留特定的其他狀態。所以能夠子類化將是非常有用的。 – marosoaie