When writing an iOS application, it's too frequently we wait until event has sent, e.g., async operation has finished, network request has responded, view has been clicked.
There're two major types of callback methodology:
Which pattern should I adopt when I designing my own callbacks, this has been a difficult decision to make between
Block when i was a junior. Now I got my best practice.
iOS developers should familiar with delegate, because delegate is the primary way that UI events are implemented in CocoaTouch. Although there is a popular repository BlocksKit in GitHub, I believe Apple still does it a right way.
The main value of delegation is that it allows you to easily customize the behavior of several objects in one central object. From Apple developer library Delegation
I use delegate pattern for almost all my views, hence my View Controller is the delegate of lots delegating objects (In this case, views). In View Controller we handling UI events from view (such as touch event) and determining states of view. Just like what View Controller should be, a coordinator.
Delegate pattern match perfectly with repeating events, for instance, a repeatable API endpoint request with modified parameters: paginator.
Delegate object simply needs to know there's a bunch of data or objects been loaded and it's time to handle it.
Delegate variants - Data source
A data source is almost identical to a delegate. The difference is in the relationship with the delegating object. Instead of being delegated control of the user interface, a data source is delegated control of data.
Implement delegate the right way
Note: Delegates should never use strong attribute. That can lead to a retain cycle.
Block is an elegant solution for one time job callbacks, especially async task. Block can be anonymous or named, depends whether it reuse or not.
For instance, computing the FFT (Fast Fourier Transform) for some data, add completion and failure callback block is well suited. In this case, we use anonymous block.
One time network request
When performing a certain network request, the most readable way is attaching our callback block just after the request. Unlike computation callback block above, it's likely we can use named block in network case, at lease error handling block can be reused.
Implement block the right way
Note: If you could reference self inside block, use variable qualifiers
__weak of self. If not, that can lead to self-block retain cycle. (Because blocks will retain any objective c values that are captured when they are created.)
Again, libextobjc had two useful marcos
@strongify, it prevent writing ugly weakSelf alias.
In some cases, we have to deal with dependent async tasks. A real-life example is we need to know what
userId is before we get
userFollowersCount from two isolated API endpoints. The input of secondary request is the output of the primary request.
If we handle this case in
Delegate way, the code looks a bit scattered. If we handle this case in
Block way, the dependency of those two requests really clear and readable, but what if we add more dependencies after it? The Nested Blocks becomes Pyramid of doom.
There are several frameworks out there can do callback chaining very well.
- PromiseKit The concept of PromiseKit is Futures and promises
Simple example took from PromiseKit doc
These frameworks create signal/promise/task/stream called in sequence, chained together. This is what called callback chaining.
Currently I use Objective C as my primary iOS language over Swift. Hence I prefer ReactiveCocoa, but using PromiseKit in some of my side projects, PromiseKit is a light-weight elegant solution. If I going to Swift, RxSwift is my first choice. Because I'm an Android developer as well.