• Category Archives: iOS

A design for iOS push notifications

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

1
2
3
4
5
6
7
8
@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.

1
2
3
4
5
6
7
8
9
10
11
@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.

1
2
3
4
5
6
@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.

Read more at the source

Testing iOS UI code with Kiwi

Testing iOS UI code with Kiwi

At TaskRabbit, we use automated testing in iOS,
Android, and web projects. When I first was talking to Julian about joining
TaskRabbit, I got super excited when I heard they were testing the iPhone app.
Testing wasn’t so popular in the iOS community at the time (not that this has
changed).

When I first started, the team was using
Cedar as a testing framework. I had used
Kiwi in past projects and was hooked. Kiwi is a treat to work with, is written
purely Objective-C and is built on top of SenTestKit/XCTest – check it out
here. We ended up switching to Kiwi to test
the app and the result has been nothing but positive.

Testing iOS UI code isn’t always so straightforward and requires special
considerations, so I wanted to share some techniques that we are using to make
testing more enjoyable.

Read more at the source
close