At TaskRabbit, we use push notifications heavily in our apps. The first implementations of push were simple blocks of conditional logic in view controllers that could respond to notifications. After we started adding many notification events to the app, we realized pain points of this design and iterated on how to implement push.
A small APNS payload
As payloads from APNS are limited in size, we use a short string to indicate
what type of event should happen in the app:
event_type. For most
notifications, the unique object id of the related model is also included.
The payload is immediately serialized to a
TRRemoteNotification - the
application’s representation of the event. We use an enum to represent the
remote event type which is serialized from the
event_type string in the
payload. An enum is easy to work with and is defined in the app at build time.
This enum also serves to ensure that is a finite set of event types.
Dispatching notifications via two simple protocols
@protocol TRUIRemoteNotificationProtocol <NSObject> - (id)initWithNotification:(TRRemoteNotification *)notification; @end @protocol TRUIRemoteNotificationResponderProtocol <NSObject> - (BOOL)isFirstResponderForNotification:(TRRemoteNotification *)notification; - (void)didReceiveNotification:(TRRemoteNotification *)notification; @end
Notifications in navigation controller based apps
We dispatch notifications immediately after serializing a
TRRemoteNotification, by setting the notification on the navigation
@interface UINavigationController (TaskRabbit) <UINavigationControllerDelegate> - (void)pushNotificationFirstResponder:(id <TRUIRemoteNotificationResponderProtocol>)responder; - (__weak id <TRUIRemoteNotificationResponderProtocol>)popNotificationFirstResponder; - (void)setNotification:(TRRemoteNotification *)notification animated:(BOOL)animated; @end
In order to respond to notifications, an object must be pushed onto the
navigation controller’s responder stack. This responder chain is like
UIResponder, but for remote events.
UIResponder, we enumerate through the responder chain,
checking if each object can respond to the notification by calling
isFirstResponderForNotification:. If the object does respond, the
notification dispatch process is complete. If not the next object in the
responder chain is given the opportunity to respond. Only 1
TRUIRemoteNotificationResponderProtocol can respond to an instance of a
Any object can sign up to respond to notifications on by adding its self to the
responder chain. In our applications, only view controllers implement these
protocols and respond to notifications.
viewWillAppear: is the point in the
view controller life-cycle where most view controllers will sign up.
Since a deallocated view controller cannot receive notifications and the
responder chain shouldn’t create a strong reference to the responder: a
NSPointerArray with weak references is the perfect collection to implement
the responder chain. The original implementation used a
NSMutableArray as a
data structure, which required each responder to be explicitly removed.
Building the same hierarchy a user would navigate to
If there is no responder for the first notification, the next step is to check if we need to build a new navigation hierarchy.
We load the notification completely before presenting the new UI stack.
At this point in the notifications life-cycle we need new view controllers to
display. We added a asynchronous factory method on
UIViewController to start the
loading process. In the case of the application being active, the user is not
notified until loading has completed.
@interface UIViewController (TRRemoteNotification) + (void)viewControllersForNotification:(TRRemoteNotification *)notification completion:(void (^)(NSArray *viewControllers))completion; @end
We save an array of
UIViewController class names that conform to
TRUIRemoteNotificationProtocol so that we can build an array of view
TRRemoteNotificationEventType has a hierarchy of view
controller classes defined.
Beyond push - deep linking support and URL schemes.
After we implemented this pattern to respond to notifications, we realized we
wanted the same functionality when the app was opened from a URL. Using the
notification protocol to handle URL was obvious because it only requires an
object id and
event_type to load the model and present the UI. Since this
design only requires 2 strings, our URL scheme is simple and the deep links are
We handle lots of push notification types as well as URL schemes in our apps. This design isn’t a magic bullet for handling remote events, but it is easier to follow and more robust than massive blocks of conditional logic.