I'm trying to abstract views that are configured from a view model. I've been using associated types so far:
public protocol ViewModelProtocol: Equatable {}
public protocol ModeledView: class {
/// The type of the view model
associatedtype ViewModel: ViewModelProtocol
var viewModel: ViewModel? { get }
/// Sets the view model. A nil value describes a default state.
func set(newViewModel: ViewModel?)
}
Which I can use like:
struct MyViewModel: ViewModelProtocol {
let foo: String
static public func == (lhs: MyViewModel, rhs: MyViewModel) -> Bool {
return lhs.foo == rhs.foo
}
}
class MyView: UIView, ModeledView {
typealias ViewModel = MyViewModel
private(set) var viewModel: MyViewModel?
public func set(newViewModel: MyViewModel?) {
print(newViewModel?.foo)
}
}
However I'd like to specify a protocol for my view models, and not a concretized type. The reason is that one struct / class could comply to several such view model protocols. I don't want to either convert this object to another type just to pass it to the view, or have the view have an associated type with more requirements than it needs. So I think I'd like to do something like:
protocol MyViewModelProtocol: ViewModelProtocol {
var foo: String { get }
}
class MyView: UIView, ModeledView {
typealias ViewModel = MyViewModelProtocol
private(set) var viewModel: MyViewModelProtocol?
public func set(newViewModel: MyViewModelProtocol?) {
print(newViewModel?.foo)
}
}
struct DataModel: MyViewModelProtocol {
let foo: String
let bar: String
static public func == (lhs: MyViewModel, rhs: MyViewModel) -> Bool {
return lhs.foo == rhs.foo && lhs.bar == rhs.bar
}
}
let dataModel = DataModel(foo: "foo", bar: "bar")
let view = MyView()
view.set(newViewModel: dataModel)
This doesn't work. The compiler says that MyView doesn't conform to the ModeledView protocol, and hints that
Possibly intended match 'MyView.ViewModel' (aka 'MyViewModelProtocol') does not conform to 'ViewModelProtocol'
I don't really get what's bothering the compiler as MyViewModelProtocol is defined as extending ViewModelProtocol
ModeledViewis a PAT (protocol with associated type), but it's clearly something that you'd want to put in a variable or an array. PATs have no existential type, so you can't do that. This is what eventually leads people down the road to type erasers, and it rapidly becomes a mess. The point of a PAT is to add methods to other types or to allow a type to be passed to a generic function, not to be a "generic protocol." Swift is evolving to allow that (forums.swift.org/t/improving-the-ui-of-generics/22814), but I wouldn't expect it for several releases. - Rob Napier