Alternatives to the delegation pattern allowing blind messages between components

delegationdesign-patternsswift-language

Delegates and delegation exist to enable passing specific messages to an observer, regardless of that observer's type. In a coordinator-type architecture, where coordinators manage and run the flow of an application (instead of say the UI layer, à la iOS & UIKit), delegation serves as the primary means of communication between child coordinators and their parents. The problem is that these delegate calls are generally quite specific to the application in question.

A typical example might look like:

protocol AuthenticationCoordinatorDelegate {
    func coordinatorDidPressLoginButton()
    func coordinatorDidPresentAuthenticationPage()
}

This protocol only applies to the authentication coordinator and is only ever implemented by its owner. It's a very specific use case used in one place and because every other coordinator needs a way to communicate, boilerplate runs rampant. The larger an application grows, the more coordinators will exist, and the more one-off delegation interfaces will have to be created, all non-generic.

What are some possible alternatives to the delegation pattern that allows for blind messaging passing between components? One thought I had was a Redux style architecture where instead of informing a delegate something has happened, an action is dispatched to a central store and the parent coordinator reacts appropriately. There are of course, severe limitations to that pattern such as how to model actions like pressing a button in the state tree, and how to persist that state tree (specifically on mobile).

Anyone have suggestions or ideas?

Best Answer

One simple way to provide anonymous linking between components is using closure properties for "notifications".

An object having a name property, for example, might also have:

var didChangeName: ((old: String, new: String) -> Void)?

Upon name being modified, it would call self.didChangeName?(old: oldName, new: newName).

Its owner or another object that was interested in the change -- such as a view in an MVVM setting -- would have set this property earlier to take whatever action was appropriate:

viewModel.didChangeName = { [weak self] (old, new) in
    self?.reactToNameChange(old, new)
}

This is quite flexible in that a) any other object can do whatever it likes with the notifications; b) that object does not have to deal with any notifications that it does not care about*; and c) though probably rarely needed, separate objects can freely register for different portions of the notification set: this is impossible with the traditional delegate setup.

On the other hand, it's a bit less flexible than a solution like KVO/Rx because only one object can "subscribe" to each closure. Also, passing notifications up a control chain is completely explicit, requiring you to link callbacks by hand at each level. For example:

// Parent object
viewModel.modelDidChange = { [weak self] in
    self?.handleModelChange()
}

// Child object
subViewModel.didChangeName = { [weak self] (old, new) in
    // Do things...
    self?.modelDidChange()
}

A second advantage is that it's extremely testable, because there is no requirement for a client other than that it has set the closure:

func testNotifiesViewOnNameChange()
{
    let newName = Model.dummy.name
    var viewModel = ViewModel(model: Model.dummy)
    var sentDidUpdate = false
    viewModel.didUpdate = { sentDidUpdate = true }

    viewModel.name = newName

    XCTAssertTrue(sentDidUpdate)
}

If you're interested in seeing this in more depth, you should have a look at Ian Keen's article on Non-reactive MVVM and the accompanying project on GitHub. That's where I learned about this technique, and I think it's an excellent explanation.


*Not possible with a strictly-Swift protocol