Reputation: 700
I'm trying to implement a datasource protocol with associated type
protocol DataSourceCompatible {
associatedtype CellModel
func cellModelForItem(at indexPath: IndexPath) -> CellModel
}
Protocol AddressBookViewModelType
inherits from base protocol and constrains the associated value to another protocol
protocol AddressBookViewModelType: class, DataSourceCompatible where CellModel == AddressBookCellModelType {
}
AddressBookViewModel
is a concrete implementation of AddressBookViewModelType
protocol
class AddressBookViewModel: AddressBookViewModelType {
func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModelType {
let contact = sectionedContacts[indexPath.section][indexPath.row]
return AddressBookCellModel(contact: contact)
}
}
The code compiles fine, however when I declare the viewmodel as a property on my viewcontroller, the compiler fails with Protocol 'AddressBookViewModelType' can only be used as a generic constraint because it has Self or associated type requirements
.
class AddressBookViewController: UIViewController {
private var viewModel: AddressBookViewModelType!
func configure(viewModel: AddressBookViewModelType) {
self.viewModel = viewModel
}
...
}
I remember seeing type erasure might solve the issue, but I'm not that familiar with type erasure concept. Is there a way to solve the issue?
Update:
How are AddressBookCellModelType and AddressBookCellModel related here?
It's a struct implementing a protocol.
protocol AddressBookCellModelType {
var name: String { get }
var photo: UIImage? { get }
var isInvited: Bool { get }
}
struct AddressBookCellModel: AddressBookCellModelType {
....
}
Upvotes: 4
Views: 3230
Reputation: 299275
To expand on my questions in the comments, looking at this code it looks like it would be exactly as flexible without adding AddressBookCellModelType
or AddressBookViewModelType
, and this would also get rid of the headaches, while still being generic over DataSourceCompatible
.
// This protocol is fine and very useful for making reusable view controllers. Love it.
protocol DataSourceCompatible {
associatedtype CellModel
func cellModelForItem(at indexPath: IndexPath) -> CellModel
}
// No need for a protocol here. The struct is its own interface.
// This ensures value semantics, which were being lost behind the protocol
// (since a protocol does not promise value semantics)
struct AddressBookCellModel {
var name: String
var photo: UIImage?
var isInvited: Bool
}
// AddressBookViewModel conforms to DataSourceCompatible
// Its conformance sets CellModel to AddressBookCellModel without needing an extra protocol
class AddressBookViewModel: DataSourceCompatible {
let sectionedContacts: [[AddressBookCellModel]] = []
func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModel {
return sectionedContacts[indexPath.section][indexPath.row]
}
}
class AddressBookViewController: UIViewController {
private var viewModel: AddressBookViewModel!
func configure(viewModel: AddressBookViewModel) {
self.viewModel = viewModel
}
}
Doing it this way allows for a generic VC without introducing more pieces that required:
class DataSourceViewController<DataSource: DataSourceCompatible>: UIView {
private var viewModel: DataSource.CellModel!
func configure(viewModel: DataSource.CellModel) {
self.viewModel = viewModel
}
}
let vc = DataSourceViewController<AddressBookViewModel>()
Upvotes: 1
Reputation: 571
It's just Swift specification that you cannot use 'protocol with associated type' as a type declaration. The reason is that the compiler can't know what the associated type will actually be at the compile time, which violates the "type-safety" of Swift.
The solution would be to use type-eraser like you said, or make the type generic.
Upvotes: -1
Reputation: 2198
Have you tried just using it as a generic, like the warning/error says:
class AddressBookViewController<T: AddressBookViewModelType> : UIViewController {
private var viewModel: T!
func configure(viewModel: T) {
self.viewModel = viewModel
}
...
}
You'd need to initialise your controller with a property of variable T
so the type can be inferred.
Upvotes: 4