I’ve recently started to do some more complicated programming with network-related work on iOS with Swift.
This introduced a lot of asynchronous code not only in networking but also some exchange of information between view controllers.
Anyway, my question is simple, and can be quite general. As far as I know, there are (at least) four ways to do callback, so is there a general rule as to which is better in each case?
- callback closure (function) as a function argument
- delegation with protocol
- notification (or observer)
- shared state object
Use cases in my application
- Network stuff, POST request or download file
- passing data one-way between view controllers on button tap
- passing data both-ways between view controllers
(One view shows the selected option, another view gives the real options for user to select)
1
Here are my views on the various observation patterns. I may update this list over time if comments seem to require it.
- Callback with closure: This is great if you have some sort of
one-shot thing (like a network response), or multiple responses that all need to be handled the same way. Not so good if you are expecting multiple responses which need to be handled differently or different types of
responses (which would require a different closure for each type of
response.) Also not helpful if you have multiple objects interested
in the response. - Delegation with protocol: Makes a lot of sense if there is a
business reason why there should only be one listener/observer and
it makes sense for that observer to handle several different state
changes. In other words, if you have more than one object that is
interested in the subject, then delegation won’t work. If you end up
with a protocol that only forces the implementation of one method,
then this pattern is likely not a good idea either. Also, if you
have an observer that wants to track several different objects that
implement the protocol in question, then you should probably
reconsider the use of this pattern. - Notification: The NSNotificationCenter works pretty well for very
generic or system wide messages. If you only have one observer, or
only expect the message to be sent once, then this is a poor choice
of pattern. Also, this pattern can save you a lot of refactoring
when you have to send a message between two disparate objects in a
mature system (i.e., a hack.) - Key value Observation: This works well if the observer only wants to
monitor the state of a single field but can get unwieldy if trying
to observe several fields or if you just want to be informed of some
signal.
Some patterns you didn’t mention:
- target/action callback: An example of this is UIControl… This has
the same benefits and tradeoffs as a callback with closure and I
think has largely been superseded by it. - Abstract method/subclass instantiation: There is the classic mode of
implementing a method in a subclass to handle requirements of the
base class. UIViewController’sviewDidLoad
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. - Futures/Promises: This pattern has the same benefits and drawbacks as callback closures, but can avoid nested closures that sometimes pop-up.
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.