Sites like Stack Overflow naturally fall into the groove of giving quick answers to common problems. Take, for example, this question, with answers containing code snippets removed from any method, or worse, answers with code injected right into the app delegate. This code shows you how to do the thing you’re trying to do, but doesn’t show you the bigger picture. Where does this stuff go? How does it talk to other components?

In this post, I hope to describe how a common subsystem of an app, such as push notifications, might be architected. It’s an object-oriented architecture, and we will talk about interfaces more and implementations less.

Our requirements are pretty straightforward:

  • Handle token registration
  • Forward push notifications recieved while backgrounded to a router object, so that the app can configure its screens properly
  • Show a toast notification for notifications received while the app is foregrounded
  • Allow any object (such as a conversation view controller) to supress the toast notification
  • Allow any object to be informed when a push notification is received, so it can execute any releveant code, such as animating in new messages

The App Delegate

Ah, my old nemesis, the app delegate. Apple uses this pattern as a central point to get information into our app. Notably, methods like - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken encourage developers to put the code right into the app delegate, quickly bloating it. We should dispatch to another object immediately. This will separate our intent from our implementation, and make the resulting code easier to read.

The Push Center

We need a central object to store state (such as which objects want to be able to suppress notifications) and to make decisions about how to handle push notifications. That object, RGPushCenter, will be a singleton. Singletons are bad, and if our object were only accessed from the app delegate, it could easily just be a property on the app delegate. However, it also needs to be accessed from elsewhere in the app (other objects need to tell it to suppress notifications), so global access is required.

There are a few things we can do to mitigate the damage a singleton can do. Primarily, our singleton will have only one point of access, which is the +sharedCenter class method. Having lots of class methods makes it hard to have state in your object, which makes it hard to refactor.

The push center is the home for code like push notification registration. If you’re tempted to just leave this code (it’s only one line!) in the app delegate, consider the complexity that might be added. For example, while I was writing this code, I learned that iOS 8 handles push notification registration differently. Because the code was hidden in the push center, I was able to easily change it, leaving the app delegate none the wiser.

- (void)registerForRemoteNotifications {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
    {
        [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    }
    else
#endif
    {
        UIRemoteNotificationType types = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert;
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:types];
    }
}

This way, the code behaves correctly for iOS 7 and 8, in Xcodes 5 and 6. Adding this to the app delegate would only serve to confuse the reader (“What is this #define for?”, “What happened in iOS 8?”, etc). Hiding things at the right level of abstraction makes it easier to understand then code when you read it later.

We should always be dispatching immediately to our push center object, like so:

- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
    [[RGPushCenter sharedCenter] didRegisterForRemoteNotificationsWithToken:deviceToken];
}  

Inside this method, we have to convert our NSData object to an NSString, and then register it to the API. You might be tempted to use a C function to convert the data to a string, but let’s initialize a special object for it.

The Push Token

RGPushToken *pushToken = [[RGPushToken alloc] initWithTokenData:token];  

It might seem like overkill to allocate memory for a whole object for our push token, but allocation is pretty cheap, all things considered. Further, once we have an object, it’s very easy to add a simple message like [pushToken registerWithAPI] to perform the API call to the server. The RGPushToken class now nicely encapsulates the NSData to NSString conversion as well as the network request, leaving the push center dumber and better off for it.

Inside the push token class, we can make a one line call to our HTTP session manager:

- (void)registerWithGeniusAPI {
    [[RGAPI manager] POST:@"/account/devices" parameters:self.POSTParameters success:nil failure:nil];
}

Because this is its own class, the -POSTparameters can be wrapped up into their own method as well. At each layer of abstraction, we push down the complexity into another abstraction until the thing is so simple that it can be understood by glancing at it.

The Push Notification

In some cases, we don’t want to dispatch immediately to the push center, but make a decision first. When receiving a push notification when the app is running, we need to make a decision based on whether or not it’s backgrounded.

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    if (application.applicationState == UIApplicationStateActive) {
        [[RGPushCenter sharedCenter] presentToastWithUserInfo:userInfo];
    } else {
        [[RGPushCenter sharedCenter] performRoutingForNotificationWithUserInfo:userInfo];
    }
}

This is the right place for this decision for a number of reasons. We don’t want the push center to have to know about different ways that the same data comes in; in fact, if it were up to me, this would probably be two delegate methods. We also already have access to the application object, and we can quickly query it’s state to send more intelligible messages to the push center, leaving it much cleaner.

What do we do with the notification dictionary once it comes into our push center? If you guessed “make an object!”, you’d be correct.

RGPushNotification *notification = [[RGPushNotification alloc] initWithDictionary:userInfo];  

Object-oriented programming is all about encapsulation. What are we encapsulating here? Whether or not the push notification is silent, the type of notification (“new message!”, “update the unread count!”), and where the app should go when the notification is activated, which is stored in the routing URL. This also gives us the flexiblity to transform or expose more as needed in the future, and localize those changes to only this class. Once we have our notification object, we can grab its routing URL and pass it a router object that handles configuring our view controllers:

[[RGNavigationManager sharedManager] handleOpenURL:notification.routingURL];  

This code could access the raw data from the userInfo dictionary, but we’d end up with stringly-typed NSURL conversion in our push center. [NSURL urlWithString:userInfo[@"genius"][@"url"]] is a lot less elegant and truthfully, we just don’t care where the URL string is or how it’s converted into an NSURL. Offload that responsibility to another class!

The Delegates

The only complex component remaining is the preventing the toast from being shown if any other objects wish to suppress it. We need to be able to ask other objects that care whether they want to prevent the toast from happening, but we don’t want to be too tightly coupled to the other objects. Of the common communication patterns, most of them (NSNotifications, KVO, target-action) can’t return data. Blocks are an interesting solution, but would require some kind of complex token-based system to deregister a block, so we will use delegates for simplicity.

Having just one delegate can be dangerous with a singleton, since another object can be take away your delegateness at any point without telling you. For safety’s sake, let’s keep multiple delegates. Order doesn’t matter, so we can use a set-like construct, but we want to it to hold a weak (not strong or unsafe) reference to each of the items in it. Forunately, the Foundation SDK gives us [NSHashTable weakObjectsHashTable], which is essentially a set with weak references, exactly what we’re interested in.

From there, we need a way to register and deregister objects as delegates.

- (void)addDelegate:(id)delegate;  
- (void)removeDelegate:(id)delegate;  

We can now write a method to check if any of the delegates want to suppress the notification.

- (BOOL)shouldSuppressNotification:(RGPushNotification *)notification {
    for (id<RGPushCenterDelegate> delegate in self.delegates) {
        if ([delegate pushCenter:self shouldSuppressNotification:notification]) {
            return YES;
        }
    }
    return NO;
}

Finally, we’ll create an NSNotification called RGPushNotificationWasReceived, which observers can listen to and perform their own actions. We already have delegates, why include a separate communication pattern here? The reason is simple. We want to keep the concept of suppressing a notification separate and decoupled from the concept of listening for notifications. Some objects will just need to know that a notification is recieved (such as the conversation list, which needs to update, but not suppress the toast). Notifications also don’t really require any knowledge of the push center class, only the push notification object, which is another reason to keep them separate.

This writeup doesn’t provide code that you can copy and paste into your own project, but it’s not particularly complex either (the actual push center is only 130 lines of code). I hope that it provides you with a framework for thinking about how to architect features like this in future.