3

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?

jscs
  • 848
  • 9
  • 17
barndog
  • 131

2 Answers2

3

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

jscs
  • 848
  • 9
  • 17
1

Well, a simple "pattern" that comes to mind is using NSNotificationCenter, something that can come handy when there is a need to update view controllers not directly connected or in cases where you need to update multiple observers at once. Be sure to remove your observers to prevent retain cycles.