Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working around the lack of recursive protocol constraints in Swift 3

Tags:

swift

swift3

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.

like image 286
Steffen D. Sommer Avatar asked Aug 18 '16 08:08

Steffen D. Sommer


1 Answers

By some reason / language shortcomings you have to use explicitly cast when assigning the delegate in View.foo: viewModel.delegate = self as? VM.D

But why do you use structs and not classes? I think you want classes, expecially you don't want all those View/ViewModel variables being copied around while modified - instead of being referenced - when you do something like

var vm = ViewModel() // Type: ViewModel
let v = View(viewModel: vm) // Type: View<ViewModel> 
vm.delegate = v

Especially

func foo() {
    viewModel.delegate = self
}

doesn't work unless you declare View.foo as mutating, and this would require almost everything (including the ViewDelegate.foo and ViewModel.bar) to be made mutating and v = View(viewModel: vm) cannot be a constant any more.

Although the solution below would also work with structs, I just changed everthing to classes:

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


class ViewModel: ViewModelType {
    typealias D = ViewDelegate
    var delegate: D?

    func bar() {
        delegate?.foo() // Access to delegate methods
    }
}

class View<VM: ViewModelType>: ViewType, ViewDelegate {
    var viewModel: VM

    // initializer needed, because we are no struct any more
    init(viewModel vm:VM) {
        self.viewModel = vm
    }

    func foo() {
        viewModel.delegate = self as? VM.D
    }
}


var vm = ViewModel() // Type: ViewModel
let v = View(viewModel: vm) // Type: View<ViewModel>
v.foo()     // View<ViewModel>
vm.delegate // View<ViewModel>

There's just one more thing: Why don't you assign the delegate in the initializer of your view class, like:

// initializer needed, because we are no struct any more
init(viewModel vm:VM) {
    self.viewModel = vm
    self.viewModel.delegate = self as? VM.D
}

Then you could skip the call to v.foo() in order to set the delegate.

like image 147
Andreas Oetjen Avatar answered Nov 05 '22 09:11

Andreas Oetjen