TaskRabbit is Hiring!

We’re a tight-knit team that’s passionate about building a solution that helps people by maximizing their time, talent and skills. We are actively hiring for our Engineering and Design teams. Click To Learn more

A Previous Employee

A design for iOS push notifications

@ 21 Mar 2014

ios


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 controller.

@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.

Similar to 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 TRRemoteNotification.

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 controllers. Each 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 short.

Conclusion

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.

Comments

Coments Loading...