The Guava Library has the concept of a ListenableFuture and a SettableFuture.
A ListenableFuture
allows you to register callbacks to be executed once the computation is complete, or if the computation is already complete, immediately. This simple addition makes it possible to efficiently support many operations that the basic Future
interface cannot support.
Because the Runnable
interface does not provide direct access to the Future
result, users who want such access may prefer Futures.addCallback
. A FutureCallback<V>
implements two methods:
onSuccess(V)
, the action to perform if the future succeeds, based on its result
onFailure(Throwable)
, the action to perform if the future fails, based on the failure
The most important reason to use ListenableFuture
is that it becomes possible to have complex chains of asynchronous operations.
When several operations should begin as soon as another operation starts ("fan-out"), ListenableFuture
just works: it triggers all of the requested callbacks. With slightly more work, we can "fan-in," or trigger a ListenableFuture to get computed as soon as several other futures have all finished: see the implementation of Futures.allAsList for an example.
In .NET, these concepts are implemented using Task
, TaskCompletionSource
and ContinueWith
.
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
Best Answer
Here are my views on the various observation patterns. I may update this list over time if comments seem to require it.
Some patterns you didn't mention:
viewDidLoad
and the like are examples. Another pattern that has been largely superseded, by protocols, but comes in handy if you happen to already have the class structure in place.Lastly, there is reactive programming (ReactiveCocoa and RxSwift for example) that can potentially take the place of all of the above patterns. It takes some time to learn but once you get the hang of it, you can handle all your observation needs with one pattern.