Swift 3 (Xcode 8 beta 6) currently has a limitation regarding "recursive protocol constraints". There is an open issue here, and there's similar discussions going on here, here and here. However, I still fail to see how one is supposed to work around this limitation. Is it possible?
Let's consider the simple example of a view referencing a view model and the other way around and lets not consider ref/value types as well as any retain cycles:
protocol ViewModelType {
associatedtype V: ViewType
var view: V { get }
}
struct ViewModel<V: ViewType>: ViewModelType {
var view: V
}
protocol ViewType {
associatedtype VM: ViewModelType
var viewModel: VM { get }
}
struct View<VM: ViewModelType>: ViewType {
var viewModel: VM
}
With the above code you will run into the Type may not reference itself as a requirement
as discussed in the provided links.
Then (naive as I am), I thought I could work around this by doing:
protocol _ViewModelType {}
protocol ViewModelType: _ViewModelType {
associatedtype V: _ViewType
var view: V { get }
}
struct ViewModel<V: ViewType>: ViewModelType {
var view: V
}
protocol _ViewType {}
protocol ViewType: _ViewType {
associatedtype VM: _ViewModelType
var viewModel: VM { get }
}
struct View<VM: ViewModelType>: ViewType {
var viewModel: VM
}
This kills the error, but it basically just postpones the problem. Because now when we want to construct our concrete types, we end up in a never-ending loop of specializations:
let vm = ViewModel<View<ViewModel<View...>>>()
I believe this is a somewhat basic constraint I would like to put in my protocols but at the moment I fail to see how to do it. Is it possible to work around this until Swift gets updated? Or do I need to start introducing less strict protocols until this has been implemented in Swift?
Update August 19, 2016
After trying a lot to figure out the best way to to work around this issue, I believe I have found a solution that is acceptable and only requires minimal compromises:
protocol ViewModelType {
associatedtype D: Any // Compromise to avoid the circular protocol constraints.
var delegate: D? { get set }
}
protocol ViewType {
associatedtype VM: ViewModelType
var viewModel: VM { get }
}
protocol ViewDelegate {
func foo()
}
struct ViewModel: ViewModelType {
typealias D = ViewDelegate
var delegate: D?
func bar() {
delegate?.foo() // Access to delegate methods
}
}
struct View<VM: ViewModelType>: ViewType, ViewDelegate {
var viewModel: VM
func foo() {
// Preferred, but not possible: viewModel.delegate = self
}
}
var vm = ViewModel() // Type: ViewModel
let v = View(viewModel: vm) // Type: View<ViewModel>
vm.delegate = v
The main idea is that, a mediator object or a delegate object is introduced, to handle the communication between the view and the view model. The reference to this delegate is of type Any
to break the circular protocol constraints. The only downside as I see it, is that the delegate needs to be set up "from the outside", from where the objects are created, and cannot be set in the View
implementation. If one tries to do this, the error Cannot assign value of type View<VM> to type _?
will appear.
However, with this approach we get the correct types without having to do a lot of specialization. Of course, one could add more protocols to have even more abstractions, but the same solution would apply.