Sandeep Bhandari
Sandeep Bhandari

Reputation: 20379

How to resolve generic property in protocol with associatedtype?

Idea is to share the common code between UITableView and UICollectionView and their viewModels. So I have a baseCollectionsViewModel which is a generic class and looks like

class BaseCollectionsViewModel<T: BasicTrackDataExtractorProtocol> {
    var baseTrackResponse: T?
    ...
}

Now I would like to have a BaseCollectionsView which holds BaseCollectionsViewModel. Because its a UITableView and UICollectionView I cant have a common base class among them so I decided to go in the direction of protocol to work around multiple inheritance

protocol BaseCollectionsViewProtocol {
    associatedtype dataExtractorType: BasicTrackDataExtractorProtocol
    var viewModel: BaseCollectionsViewModel<dataExtractorType>! { get }
    //all other common properties and logic here
}

Finally when I try to use this protocol in my final class

final class TrackingTableViewModel: BaseCollectionsViewModel<TrackOrderV3> {
     
}

final class TrackingTableView: UITableView,BaseCollectionsViewProtocol {
    var viewModel: TrackingTableViewModel!
    typealias dataExtractorType = TrackOrderV3
    //other code
}

Compiler complains that TrackingTableView does not confirm to BaseCollectionsViewProtocol and keeps adding

final class TrackingTableView: UITableView,BaseCollectionsViewProtocol {
    var viewModel: BaseCollectionsViewModel<TrackOrderV3>!
    typealias dataExtractorType = TrackOrderV3

Is this because BaseCollectionsViewModel<TrackOrderV3> and TrackingTableViewModel are invariant? Is there anyway I can resolve this problem with TypeErasure?

If its not a protocol I could solve it as

class TestTableView<T: BasicTrackDataExtractorProtocol, U: BaseCollectionsViewModel<T>> {
    var viewModel: U!
}

class TestTrackingTableView: TestTableView<TrackOrderV3, TrackingTableViewModel> {
    func testViewModel() {
        let _ = self.viewModel.numberOfRows
    }
}

But the issue is now I cant say class TestTrackingTableView: TestTableView<TrackOrderV3, TrackingTableViewModel>, UITableView because multiple inheritance is not allowed.

P.S: Sorry for the highly confusing title, couldn't come up with a better title, will change if someone can suggest a better one :)

Upvotes: 0

Views: 108

Answers (1)

Rob C
Rob C

Reputation: 5073

What I would do is define a protocol for your view model:

protocol CollectionsViewModel {
    associatedtype DataType: BasicTrackDataExtractorProtocol
    var baseTrackResponse: DataType? { get }
}

Then implement a generic view model class that houses all of your common logic:

class BaseCollectionsViewModel<T: BasicTrackDataExtractorProtocol>: CollectionsViewModel {
    typealias DataType = T
    var baseTrackResponse: T?
}

Rather than setting the data model as a type constraint on the view protocol set the view model as a type constraint. This will give us greater flexibility down the road.

protocol BaseCollectionsViewProtocol {
    associatedtype ViewModelType: CollectionsViewModel
    var viewModel: ViewModelType! { get }
}

Now we can implement our final classes:

final class TrackingTableViewModel: BaseCollectionsViewModel<TrackOrderV3> {

}

final class TrackingTableView: UITableView, BaseCollectionsViewProtocol {
    var viewModel: TrackingTableViewModel!
}

Upvotes: 2

Related Questions