13. Push Notifications

When off-device services need to communicate directly with users, push notifications provide a solution. Just as local notifications allow apps to contact users at scheduled times, push notifications deliver messages from web-based systems. Push notifications let devices display an alert, play a custom sound, or update an application badge. In this way, off-device services connect with an iOS-based client, letting them know about new data or updates.

Unlike most other iOS development arenas, nearly all the push story takes place off the device. Developers create web-based services to manage and deploy information updates. Push notifications transmit those updates to specific devices. In this chapter, you learn how push notifications work and dive into the details needed to create your own push-based system.

Introducing Push Notifications

Push notifications, also called remote notifications, refer to a kind of message sent to iOS devices by an outside service. These push-based services work with any kind of application that normally checks for information updates. For example, a service might poll for new updates on Facebook, Twitter, or Google Plus; scan for nearby dangerous weather systems; respond to sensors for in-home security systems; or invite you to a shared conference. When new information becomes available for a client, the service pushes that update through Apple’s remote notification system. The notification transmits directly to the device, which has registered to receive those updates.

The key to push is that these messages originate from outside the device. They are part of a client-server paradigm that enables web-based server components to communicate with iOS clients through an Apple-supplied service. With push, developers can send nearly instant updates to iOS devices that don’t rely on users launching a particular application. Instead, processing occurs on the server side of things. When push messages arrive, the iOS client can respond by displaying a badge, playing a sound, and/or showing an alert box.

Moving application logic to a server limits client-side complexity, allowing many clients to share a single update system. Consider the weather danger service just mentioned. A dedicated site can monitor NOAA and other atmospheric feeds, sending remote notifications to each subscribed client as needed. Offsite processing also provides energy savings for iOS-based applications. They can now rely on push rather than using the iOS device’s local CPU resources to monitor and react to important information changes.

Push’s reason for being is not only tied into local resources, but it also offers a valuable solution for communicating with web-based services that goes beyond poll-and-update applications. For example, push might enable you to hook into a coaching service that transmits helpful affirmations even when an application isn’t running or to a gaming service that sends you reminder notices about an upcoming tournament.

From social networking to monitoring RSS feeds, push enables iOS users to keep on top of asynchronous data feeds. It offers a powerful solution for connecting iOS clients to web-based systems of all kinds. With push, the services you write can connect to your installed iOS base and communicate updates in a clean, functional manner.

How Push Works

Push notifications are tied to specific applications and require several security checks. A push provider (the server component that generates push requests and transmits them to Apple) can communicate only with those iOS devices that host its application, that are online, and that have opted to receive remote messages. Users have the ultimate say in push updates. They can allow or disallow that kind of communication, and a well-written application lets users opt-in and opt-out of the service at will.

The chain of communication between server and client works like this. Push providers deliver message requests through a central Apple server and via that server to their iOS clients. In normal use, the server triggers on some event (like new mail or an upcoming appointment) and generates notification data aimed at a specific iOS device. It sends this message request to the Apple Push Notification Service (APNS). This notification uses JSON formatting and is limited to 256 bytes each, so the information that can be pushed through on that message is quite limited. This formatting and size ensures that APNS limits bandwidth to the tightest possible configuration.

APNS offers a centralized system that negotiates communication with iOS devices in the real world. It passes the message through to the designated device. A handler on the iOS device decides how to process the message. As Figure 13-1 shows, push providers talk to APNS, sending their message requests, and APNS talks to iOS devices, relaying those messages to handlers on the unit.

Image

Figure 13-1. Providers send messages through Apple’s centralized push notification service to communicate with an iOS device.

Multiple Provider Support

APNS was built to support multiple provider connections, enabling many services to communicate with it at once. It offers multiple gateways into the service so that each push service does not have to wait for availability before sending its message. Figure 13-2 illustrates the many-to-many relationship between providers and iOS devices. APNS enables providers to connect at once through multiple gateways. Each provider can push messages to many different devices.

Image

Figure 13-2. Apple’s Push Notification Service (APNS) offers many gateways on its provider-facing side, enabling multiple providers to connect in parallel. Each push provider may connect to any number of iOS devices.

Security

Security is a primary component of remote notifications. The push provider must sign up for a secure sockets layer (SSL) certificate for each application it works with. Services cannot communicate with APNS unless they authenticate themselves with this certificate. They must also provide a unique key called a token that identifies a specific application on a single device.

After receiving an authenticated message and device token, APNS contacts the device in question. Each iOS device must be online in some way to receive a notification. They can be connected to a cellular data network or to a Wi-Fi hotspot. APNS establishes a connection with the device and relays the notification request. If the device is offline and the APNS server cannot make a connection, the notification is queued for later delivery.

Upon receiving the request, iOS performs a number of checks. Push requests are ignored when the user disables push updates for a given application; users can do so in the Settings application on their device. When updates are allowed, and only then, the iOS device determines whether the client application is currently running. If so, it sends a message directly to the running application via the application delegate. If not, it performs some kind of alert, whether displaying text, playing a sound, or updating a badge.

When an alert displays, users typically have the option to close the alert or tap View. If they choose View, the iOS device launches the application in question and sends it the notification message that it would have received while running. If the user taps Close, the notification gets ignored and the application does not launch.

This pathway, from server to APNS to device to application, forms the core flow of push notifications. Each stage moves the message along the way. Although the multiple steps may sound extensive, in real life, the notification arrives almost instantaneously. After you set up your certificates, identifiers, and connections, the actual delivery of information becomes trivial. Nearly all the work lies in first setting up that chain and then in producing the information you want to deliver.

Make sure you treat all application certificates and device tokens as sensitive information. When storing these items on your server, you must ensure that they are not generally accessible. Should this information hit the wild, it could be exploited by third parties. This would likely result in Apple revoking your SSL push certificate. This would disable all remote notifications for any apps you have sold and might force you to pull the application from the store.

Push Limitations

In real-world scenarios, push notifications are not as reliable as you might want. They can be fairly flaky. Apple does not guarantee the delivery of each notification or the order in which notifications arrive. Never send vital information by push. Reserve this feature for helpful notifications that update and inform the user, but that the user can miss without serious consequence.

Items in the push delivery queue may be displaced by new notifications. That means that notifications may compete and may get lost along the way. Although Apple’s feedback service reports failed deliveries (that is, messages that cannot be properly sent through the push service, specifically to applications that have been removed from a device), you cannot retrieve information regarding bumped notifications. From the APN service point of view, a lost message was still successfully “delivered.”

Push Notifications Versus Local Notifications

iOS provides both remote push notification and local notification support. Local notifications, which are discussed in the Core iOS Developer’s Cookbook, are created and scheduled on-device. Push notification originates from a remote server and are sent to the device through APNS. This chapter exclusively covers remote notifications.

Provisioning Push

To start push development, you must visit Apple’s iOS developer Provisioning Portal. This portal is located at http://developer.apple.com/ios/manage/overview/index.action. Sign in with your iOS developer credentials to gain access to the site. Here at the portal, you can work through the steps needed to create a new application identifier that can be associated with a push service.

The following sections walk you through the process. You see how to create a new identifier, generate a certificate, and request a special provisioning profile so that you can build push-enabled applications. Without a push-enabled profile, your application will not receive remote notifications.

Generate a New Application Identifier

At the iOS provisioning portal, click App IDs. You can find this option in the column on the left side of the web page. This opens a page that enables you to create new application identifiers. Each push service is based on a single identifier, which you must create and then set to allow remote notification. You cannot use a wildcard identifier with push applications; every push-enabled app demands a unique identifier.

In the App IDs section, click New App ID; this button appears at the top right of the web page. When clicked, the site opens a new Create App ID page, similar to Figure 13-3. Enter a name that describes your new identifier, such as Tutorial Push Application and a new bundle identifier.

Image

Figure 13-3. Create a new application identifier at the iOS provisioning portal.

These IDs typically use reverse domain patterns, such as com.domainname.appname and com.sadun.pushtutorial. The identifier must be unique and may not conflict with any other registered application identifier in Apple’s system. Leave the bundle seed set to your team identifier unless you have some compelling reason to do otherwise.

Click Submit to add the new identifier. This adds the app ID irrevocably to Apple’s system, where it is now registered to you. You return to the App ID page with its list of identifiers and are now ready to establish that identifier as push-compliant.


Note

Apple does not provide any way to remove an application identifier from the program portal after it has been created.


Generate Your SSL Certificate

On the App ID page, you can see which identifiers work with push and which do not. The Apple Push Notification column shows whether push has been enabled for each app ID. The three states for this column are the following:

• Unavailable (gray) for IDs that are no longer available

• Configurable (yellow) for apps that can be used with push but that haven’t yet been set up to do so

• Enabled (green) for apps that are ready for push

In the push column, you can find two dots for each application identifier: one for Development and another for Production. These options are configured separately. Locate your new app ID, make sure the yellow Configurable for Development is shown, and click Configure. This option appears in the rightmost column. When clicked, the browser opens a new Configure App ID page that permits you to associate your identifier with the push notification service.

An Enable Push Notification Services check box appears about halfway down the page. Check this box to start the certificate creation process. When checked, the two Configure buttons on the right side of the page become enabled. Click a button. A page of instructions loads, showing you how to proceed. It guides you through creating a secure certificate that will be used by your server to sign messages it sends to the APNS.

As instructed, launch the Keychain Access application. This application is located on your Macintosh in the /Applications/Utilities folder. When launched, choose Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority. You need to perform this step again even if you’ve already created previous requests for your developer and distribution certificates. The new request adds information that uniquely identifies the SSL certificate.

After the Certificate Assistant opens, enter your e-mail address and add a recognizable common name, such as Push Tutorial App. This common name is important. It will come in handy for the future, so choose one that is easy to identify and accurately describes your project. The common name lets you distinguish otherwise similar looking keychain items from each other in the OS X Keychain Access utility.

After specifying a common name, choose Saved to Disk and click Continue. The Certificate Assistant prompts you to choose a location to save to (the Desktop is handy). Click Save, wait for the certificate to be generated, and then click Done. Return to your Web browser and click Continue. You are now ready to submit the certificate-signing request.

Click Choose File and navigate to the request you just generated. Select it and click Choose. Click Generate to build your new SSL push service certificate. This can take a minute or two, so be patient, and do not close the Web page. When the certificate has been generated, click Continue. Retrieve the new certificate by clicking Download. Finally, click Done. You return to the App ID page where a new, green Enabled indicator should appear next to your app ID. Apple also e-mails you a confirmation that your certificate request was approved.


Note

Should you ever need to download the public key portion of your SSL certificate again, click Configure to return to the Configure App ID page. There, you can click Download to request another copy. As a rule, you should export the private+public portions of the certificate into a p12 file for safekeeping. If you lose the private key, you’ll have to regenerate the entire certificate.


If you plan to run your Push Server from your Macintosh (handy for development before deploying a server, or before the server piece is fully developed), add the new item to your keychain. It will appear in your Certificates. You can identify the certificate by clicking the small triangle next to it to reveal the common name you used when creating the certificate request.

Push-Specific Provisions

You can’t use wildcard team provisioning profiles for push-enabled applications. Instead, you must create a single profile for just that application. This means that if you intend to create development, ad hoc, and distribution versions of your app, you must request three new mobile provisioning profiles in addition to whatever provisioning profiles you have already created for other work.

In the latest versions of Xcode, you can build your profile from the Organizer (Command-Shift-2 > Devices > Provisioning Profiles > New). Enter a profile name, choose the already-configured App ID, and select the devices you want to work with. There’s a Select All button to make this easier. Click Next and let Xcode do the rest of the work for you.

Alternatively, go to the Provisioning section of the developer portal. Select Development or Distribution profile by clicking the appropriate tab. Click New Profile to begin creating your new provision. A Create Provisioning Profile page opens:

Development Provision—For development, enter a profile name, such as Push Tutorial Development. Check the certificate you will be using and choose your application identifier from the pop-up list. Select the devices you will be using and click Submit.

Distribution Provision—For distribution, select App Store or Ad Hoc. Enter a name for your new provision such as Push Tutorial Distribution or Push Tutorial Ad Hoc. Choose your application identifier from the pop-up list. For ad-hoc distribution only, select the devices to include in your provision. Click Submit to finish.

It may take a moment or two for your profile to generate. Wait a short while and reload the page. The provision status should change from Pending to Active. Download your new provision and add it to Xcode through the Organizer > Devices > Provisioning Profiles. Click Import, select the provision from your downloads folder, and click Open.

Creating a Push-Compatible Application

After installing your push-enabled provisioning profile into Xcode, you are ready to create a push-compatible application. Start by ensuring that your new bundle identifier matches the provision you just created in TARGETS > Info > Bundle identifier. Then, in Build Settings > Code Signing Identity, choose the developer identity for the new provisioning profile. You use separate identities and profiles for debug and release builds.

Registering Your Application

Signing an application with a push-compatible mobile provisioning profile is just the first step to working with push notifications. The application must request to register itself with the iOS device’s remote notification system. You do this with a single UIApplication call, as follows. The application: didFinishLaunchingWithOptions: delegate method provides a particularly convenient place to call this:

[[UIApplication sharedApplication]
    registerForRemoteNotificationTypes:types];

This call tells iOS that your application wants to accept push messages. The types you pass specify what kinds of alerts your application will receive. iOS offers three types of notifications:

UIRemoteNotificationTypeBadge—This notification adds a red badge to your application icon on SpringBoard.

UIRemoteNotificationTypeSound—Sound notifications let you play sound files from your application bundle.

UIRemoteNotificationTypeAlert—This style displays a text alert box in SpringBoard or any other application with a custom message using the alert notification.

Choose the types you want to use and or them together. They are bit flags, which combine to tell the notification registration process how you want to proceed. For example, the following flags allow alerts and badges, but not sounds:

types = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert;

Performing the registration updates user settings and allows users to customize their push preferences from Settings > Notifications. There, users can choose how to present the notification alert style (banner, alert, and none), which types to allow (badge, alert, and sound), whether to show the alert on the lock screen, and so forth.

To remove your application from active participation in push notifications, send unregisterForRemoteNotifications. This unregisters your application for all notification types and does not take any arguments:

[[UIApplication sharedApplication] unregisterForRemoteNotifications];

Retrieving the Device Token

Your application cannot receive push messages until it generates and delivers a device token to your server. It must send that device token to the offsite service that pushes the actual notifications. Recipe 13-1, which follows this section, does not implement server functionality. It provides only the client software.

A token is tied to one device. In combination with the SSL certificate, it uniquely identifies the iOS device and can be used to send messages back to the device in question. Be aware that device tokens can change after you restore device firmware.

Device tokens are created as a byproduct of registration. Upon receiving a registration request, iOS contacts the Apple Push Notification Service. It uses a SSL request. Somewhat obviously, the unit must be connected to the Internet. If it is not, the request will fail. With a live connection, iOS forwards the request to APNS and waits for it to respond with a device token.

APNS builds the device token and returns it to iOS, which in turn passes it back to the application via an application delegate callback, namely application:didRegisterForRemoteNotificationsWithDeviceToken:. Your application must retrieve the device token and pass it to the provider component of your service, where it needs to be stored securely. Anyone who gains access to a device token and the application’s push credentials could spam messages to devices. You must treat this information as sensitive and protect it accordingly.


Note

At times, the token may take time to generate. Consider designing around possible delays into your application by registering at each application run. Until the token is created and uploaded to your site, you cannot provide remote notifications to your users.


Handling Token Request Errors

At times, APNS cannot create a token or your device may not send a request. For example, you cannot generate tokens from the simulator. A UIApplicationDelegate method application: didFailToRegisterForRemoteNotifications-WithError: lets you handle these token request errors. For the most part, you need to retrieve the error and display it to the user:

// Provide a user explanation for when the registration fails
- (void)application:(UIApplication *)application
    didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    NSString *status = [NSString stringWithFormat:
        @"%@ Registration failed. Error: %@", pushStatus(),
        error.localizedFailureReason];
    tbvc.textView.text = status;
}

Responding to Notifications

iOS uses a set chain of operations (see Figure 13-4) to respond to push notifications. When an application runs, the notification is sent directly to a UIApplicationDelegate method, application:didReceiveRemoteNotification:. The payload, which is sent in JSON format, is converted automatically into an NSDictionary, and the application is free to use the information in that payload however it wants. As the application is already running, no further sounds, badges, or alerts are invoked.

Image

Figure 13-4. Visible and audible notification are presented only when the application is not running. Should the user tap on the alert in Notification Center, the application launches, and the payload is sent as a notification to the application delegate.

// Handle an actual notification
- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    NSString *status = [NSString stringWithFormat:
        @"Notification received: %@",userInfo.description];
    tbvc.textView.text = status;
    NSLog(@"%@", userInfo);
}

When an application is not running, iOS performs all requested notifications that are allowed by registration and by user settings. These notifications may include playing a sound, badging the application, and displaying an alert. Playing a sound can also trigger iPhone vibration when a notification is received.

By default, notifications are added to the Notification Center (see Figure 13-5). (Users can disable this via Settings > Notification > Application Name > Notification Center.) When the user taps the notification in the list, the application launches with the notification as a launch option.

Image

Figure 13-5. Remote alerts can appear on the Lock Screen, in SpringBoard, in applications, and in the Notification Center. Users may slide the alert on the lockscreen or tap it in Notification Center to switch to the notifying application. In this case, that application is HelloWorld, whose name is clearly shown on the alert.

Upon launch, the application delegate receives the same remote notification callback that an already running application would have seen. Alerts appear on the lock screen when the device is locked and in the Notification Center, unless the user overrides the default settings.

Recipe: Push Client Skeleton

Recipe 13-1 introduces a basic client that allows users to register and unregister for push notifications. The interface (shown in Figure 13-6) uses three switches that control the services to be registered. When the application launches, it queries the app’s enabled remote notification types and updates the switches to match. Thereafter, the client keeps track of registrations and unregistrations, adjusting the switches to keep sync with the reality of the settings.

Image

Figure 13-6. The Push Client skeleton introduced in Recipe 13-1 lets users specify which services they want to register.

Two buttons at the top left and right of the interface let users unregister and register their application. Unregistering disables all services associated with the app. It provides a clean sweep. In contrast, registering apps requires flags to indicate which services are requested.

When requesting new services, the user is always prompted to approve. Figure 13-7 shows the dialog that appears. The user must confirm by explicitly granting the application permission. If the user does not, by tapping Don’t Allow, the flags remain at their previous settings.

Image

Figure 13-7. Users must explicitly grant permission for an application to receive remote notifications.

Unfortunately, the confirmation dialog does not generate a callback when it is dismissed, regardless of whether the user agreed. To catch this event, you can listen for a general notification (UIApplicationDidBecomeActiveNotification) that gets generated when the dialog returns control to the application. It’s a hack, but it lets you know when the user responded and how the user responded. In Recipe 13-1, the confirmationWasHidden: method catches this notification and updates the switches to match any new registration settings.

Being something of a skeletal system, this push client doesn’t actually respond to push notifications beyond showing the contents of the user info payload that gets delivered. Figure 13-8 illustrates the actual payload that was sent in Figure 13-6. This display is performed in the application:didReceiveRemoteNotification: method in the application delegate.

Image

Figure 13-8. The aps dictionary may include badge requests, sound file names, and/or an alert string. The action-loc-key is less important now due to the Notification Center. If the user disables the Notification Center (this is rare), they will see the old-style alert, which displays the action button.


Note

The three sound files included in the online sample project (ping1.caf, ping2.caf, and ping3.caf) let you test sound notifications with real audio.


Recipe 13-1. Push Client Skeleton


// Important: Set up your own app id and provisioning profile
// before attempting to play with this code

@implementation TestBedViewController
@synthesize textView;

// Basic status
NSString *pushStatus ()
{
    return [[UIApplication sharedApplication]
        enabledRemoteNotificationTypes] ?
        @"Notifications were active for this application" :
        @"Remote notifications were not active for this application";
}

// Fetch the current switch settings
- (NSUInteger) switchSettings
{
    NSUInteger settings = 0;
    if (badgeSwitch.isOn) settings =
        settings | UIRemoteNotificationTypeBadge;
    if (alertSwitch.isOn) settings =
        settings | UIRemoteNotificationTypeAlert;
    if (soundSwitch.isOn) settings =
        settings | UIRemoteNotificationTypeSound;
    return settings;
}

// Change the switches to match reality
- (void) updateSwitches
{
    NSUInteger rntypes = [[UIApplication sharedApplication]
        enabledRemoteNotificationTypes];
    badgeSwitch.on = (rntypes & UIRemoteNotificationTypeBadge);
    alertSwitch.on = (rntypes & UIRemoteNotificationTypeAlert);
    soundSwitch.on = (rntypes & UIRemoteNotificationTypeSound);
}

// Register application for the services set out by the switches
- (void) registerServices
{
    if (![self switchSettings])
    {
        textView.text = [NSString stringWithFormat:
            @"%@ Nothing to register. Skipping.", pushStatus()];
        [self updateSwitches];
        return;
    }

    NSString *status = [NSString stringWithFormat:
        @"%@ Attempting registration", pushStatus()];
    textView.text = status;
    [[UIApplication sharedApplication]
        registerForRemoteNotificationTypes:[self switchSettings]];
}

// Unregister application for all push notifications
- (void) unregisterServices
{
    NSString *status = [NSString stringWithFormat:@"%@ Unregistering.",
        pushStatus()];
    textView.text = status;

    [[UIApplication sharedApplication] unregisterForRemoteNotifications];
    [self updateSwitches];
}

- (void) loadView
{
    self.view = [[UIView alloc] init];

    // Workaround for catching the end of user choice
    TestBedViewController __weak *weakself = self;
    [[NSNotificationCenter defaultCenter]
        addObserverForName:UIApplicationDidBecomeActiveNotification
        object:nil queue:[NSOperationQueue mainQueue]
        usingBlock:^(NSNotification *notification)
    {
        [[UIApplication sharedApplication]
            registerForRemoteNotificationTypes:
                [weakself switchSettings]];
         [weakself updateSwitches];
     }];

    self.navigationItem.rightBarButtonItem =
        BARBUTTON(@"Register", @selector(registerServices));
    self.navigationItem.leftBarButtonItem =
        BARBUTTON(@"Unregister", @selector(unregisterServices));
    [self updateSwitches];
}
@end

#pragma mark – Application Delegate
@implementation TestBedAppDelegate
// Retrieve the device token
- (void)application:(UIApplication *)application
    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    NSUInteger rntypes = [[UIApplication sharedApplication]
        enabledRemoteNotificationTypes];
    NSString *results = [NSString stringWithFormat:
        @"Badge: %@, Alert:%@, Sound: %@",
        (rntypes & UIRemoteNotificationTypeBadge) ? @"Yes" : @"No",
        (rntypes & UIRemoteNotificationTypeAlert) ? @"Yes" : @"No",
        (rntypes & UIRemoteNotificationTypeSound) ? @"Yes" : @"No"];

    // Show the retrieved Device Token
    NSString *status = [NSString stringWithFormat:
        @"%@ Registration succeeded. Device Token: %@ %@",
        pushStatus(), deviceToken, results];
    tbvc.textView.text = status;
    NSLog(@"deviceToken: %@", deviceToken);

    // Write token to file for easy retrieval in iTunes.
    // Handy for learning push dev – but not a great idea for
    // App Store deployment. Set UIFileSharingEnabled to YES
    [deviceToken.description writeToFile:[NSHomeDirectory()
        stringByAppendingPathComponent:@"Documents/DeviceToken.txt"]
        atomically:YES encoding:NSUTF8StringEncoding error:nil];
}

// Respond to failed registration
- (void)application:(UIApplication *)application
    didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    NSLog(@"Error registering for remote notifications: %@",
        error.localizedFailureReason);
    NSString *status = [NSString stringWithFormat:
        @"%@ Registration failed. Error: %@",
        pushStatus(), error.localizedFailureReason];
    tbvc.textView.text = status;
}

// Handle an actual notification
- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    NSString *status = [NSString stringWithFormat:
        @"Notification received: %@", userInfo.description];
    tbvc.textView.text = status;
    NSLog(@"%@", userInfo);
}

// Little work-around for showing text
- (void) showString: (NSString *) aString
{
    tbvc.textView.text = aString;
}

// Report the notification payload when launched by alert
- (void) launchNotification: (NSNotification *) notification
{
    // Workaround allows the text view to be created if needed first
    [self performSelector:@selector(showString:)
         withObject:[[notification userInfo] description]
         afterDelay:1.0f];
}

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    tbvc = [[TestBedViewController alloc] init];
    UINavigationController *nav =
        [[UINavigationController alloc] initWithRootViewController:tbvc];
    window.rootViewController = nav;
    [window makeKeyAndVisible];

    // Listen for remote notification launches
    [[NSNotificationCenter defaultCenter]
        addObserver:self selector:@selector(launchNotification:)
        name:@"UIApplicationDidFinishLaunchingNotification" object:nil];

    NSLog(@"Launch options: %@", launchOptions);
    return YES;
}
@end



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Advanced-Cookbook and go to the folder for Chapter 13.


Building Notification Payloads

Delivering push notification through APNS requires three things: your SSL certificate, a device ID, and a custom payload with the notification you want to send. The payload uses JSON formatting. You’ve already read about generating the certificate and producing the device identifiers, which you need to pass up to your server. Building the JSON payloads basically involves transforming a small well-defined dictionary into JSON format.

JavaScript Object Notation (JSON) is a simple data interchange format based on key-value pairs. The JSON Web site (www.json.org) offers a full syntax breakdown of the format, which allows you to represent values that are strings, numbers, and arrays. The APNS payload consists of up to 256 bytes, which must contain your complete notification information.

Notification payloads must include an aps dictionary. This dictionary defines the properties that produce the sound, badge, and/or alert sent to the user. In addition, you can add custom dictionaries with any data you need to send to your application, so long as you stay within the 256-byte limit. Figure 13-8 shows the hierarchy for basic (non-localized) alerts.

The aps dictionary contains one or more notification types. These include the standard types you’ve already read about: badges, sounds, and alerts. Badge and sound notifications each take one argument. The badge is set by a number, and the sound is set by a string that refers to a file already inside the application bundle. If that file is not found (or the developer passes default as the argument), a default sound plays for any notification with a sound request. When a badge request is not included, iOS removes any existing badge from the application icon.

Localized Alerts

When working with localized applications, construct your aps > alert dictionary with two additional keys. Use loc-key to pass a key that is defined in your application’s Localizable.strings file. iOS looks up the key and replaces it with the string found for the current localization.

At times, localization strings use arguments like %@ and %n$@. Should that hold true for the localization you are using, you can pass those arguments as an array of strings via loc-args. As a rule, Apple recommends against using complicated localizations because they can consume a major portion of your 256-byte bandwidth.

Transforming from Dictionary to JSON

After you design your dictionary, you must transform it to JSON. The JSON format is simple but precise. The NSJSONSerialization class creates a JSON string. Here’s the basic call:

NSData *jsonData = [NSJSONSerialization
    dataWithJSONObject:mainDict options:0 error:nil];

Custom Data

So long as your payload has room left, keeping in mind your tight byte budget, you can send additional information in the form of key-value pairs. These custom items can include arrays and dictionaries as well as strings, numbers, and constants. You define how to use and interpret this additional information. The entire payload dictionary is sent to your application, so whatever information you pass along will be available to the application:didReceiveRemoteNotification: method via the user dictionary.

A dictionary containing custom key-value pairs does not need to present an alert for end-user interaction; doing so allows your user to choose to open your application if it isn’t running. If your application is already launched, the key-value pairs arrive as a part of the payload dictionary.

Receiving Data on Launch

When your client receives a notification, tapping on it launches your application. Then, after launching, iOS sends your application delegate an optional callback. The delegate recovers its notification dictionary by implementing a method named application:didFinishLaunchingWithOptions:.

iOS passes the notification dictionary to the delegate method via the launch options parameter. For remote notifications, this is the official callback to retrieve data from an alert-box launch. The didReceiveRemoteNotification: method is not called when iOS receives a notification and the application is not running.

This “finished launching” method is actually designed to handle many different launch circumstances. Push notification is just one. Others include opening by local notifications, by URL scheme handling, and so forth. In any case, the method must return a Boolean value. As a rule, return YES if you processed the request or NO if you did not. This value is actually ignored in the case of remote notification launches, but you must still return a value.


Note

When your user taps Close and later opens your application, the notification is not sent on launch. You must check in with your server manually to retrieve any new user information. Applications are not guaranteed to receive alerts. In addition to tapping Close, the alert may simply get lost. Always design your application so that it doesn’t rely solely on receiving push notifications to update itself and its data.


Recipe: Sending Notifications

The notification process involves several steps (see Figure 13-9). First, you build your JSON payload, which you just read about. Next, you retrieve the SSL certificate and the device token for the unit you want to send to. How you store these is left up to you, but you must remember that these are sensitive pieces of information. Open a secure connection to the APNS server. Finally, you handshake with the server, send the notification package, and close the connection.

Image

Figure 13-9. The steps for sending remote notifications.

This is the most basic way of communicating and assumes you have just one payload to send. In fact, you can establish a session and send many packets at a time; however, that is left as an exercise for the reader as is creating services in languages other than Objective-C. The Apple Developer Forums (devforums.apple.com) host ongoing discussions about push providers and offer an excellent jumping off point for finding sample code for PHP, Python, Ruby, Perl, and other languages.

Be aware that APNS may react badly to a rapid series of connections that are repeatedly established and torn down. If you have multiple notifications to send at once, go ahead and send them during a single session. Otherwise, APNS might confuse your push deliveries with a denial-of-service attack.

Recipe 13-2 demonstrates how to send a single payload to APNS, showing the steps needed to implement the fourth and final box in Figure 13-9. The recipe is built around code developed by Stefan Hafeneger and uses Apple’s ioSock sample source code. It’s been cleaned up a bit, since the previous edition of this Cookbook, to deal with deprecated APIs.

The individual server setups vary greatly depending on your security, databases, organization, and programming language. Recipe 13-2 demonstrates a minimum of what is required to implement this functionality and serves as a template for your own server implementation in whatever form this might take.

Sandbox and Production

Apple provides both sandbox (development) and production (distribution) environments for push notification. You must create separate SSL certificates for each. The sandbox helps you develop and test your application before submitting to App Store. It works with a smaller set of servers and is not meant for large-scale testing. The production system is reserved for deployed applications that have been accepted to App Store:

• The sandbox servers are located at gateway.sandbox.push.apple.com, port 2195.

• The production servers are located at gateway.push.apple.com, port 2195.


Note

Recipe 13-2 is meant to be compiled for and used on the Macintosh as a command-line utility.


Recipe 13-2. Pushing Payloads to the APNS Server


// Adapted from code by Stefan Hafeneger
- (BOOL) push: (NSString *) payload
{
    otSocket socket;
    SSLContextRef context;
    SecKeychainRef keychain;
    SecIdentityRef identity;
    SecCertificateRef certificate;
    OSStatus result;

    // Ensure device token
    if (!deviceTokenID)
    {
        fprintf(stderr, "Error: Device Token is nil ");
        return NO;
    }

    // Ensure certificate
    if (!certificateData)
    {
        fprintf(stderr, "Error: Certificate Data is nil ");
        return NO;
    }

    // Establish connection to server.
    PeerSpec peer;
    result = MakeServerConnection("gateway.sandbox.push.apple.com",
        2195, &socket, &peer);
    if (result)
    {
        fprintf(stderr, "Error creating server connection ");
        return NO;
    }

    // Create new SSL context.
    result = SSLNewContext(false, &context);
    if (result)
    {
        fprintf(stderr, "Error creating SSL context ");
        return NO;
    }

    // Set callback functions for SSL context.
    result = SSLSetIOFuncs(context, SocketRead, SocketWrite);
    if (result)
    {
        fprintf(stderr, "Error setting SSL context callback functions ");
        return NO;
    }

    // Set SSL context connection.
    result = SSLSetConnection(context, socket);
    if (result)
    {
        fprintf(stderr, "Error setting the SSL context connection ");
        return NO;
    }

    // Set server domain name.
    result = SSLSetPeerDomainName(context,
        "gateway.sandbox.push.apple.com", 30);
    if (result)
    {
        fprintf(stderr, "Error setting the server domain name ");
        return NO;
    }

    // Open keychain.
    result = SecKeychainCopyDefault(&keychain);
    if (result)
    {
        fprintf(stderr, "Error accessing keychain ");
        return NO;
    }

    // Create certificate from data
    CFDataRef data = (__bridge CFDataRef) self.certificateData;
    certificate = SecCertificateCreateWithData(
        kCFAllocatorDefault, data);
    if (!certificate)
    {
        printf("Error creating certificate from data ");
        return nil;
    }

    // Create identity.
    result = SecIdentityCreateWithCertificate(keychain, certificate,
        &identity);
    if (result)
    {
        fprintf(stderr, "Error creating identity from certificate ");
        return NO;
    }

    // Set client certificate.
    CFArrayRef certificates = CFArrayCreate(NULL,
        (const void **)&identity, 1, NULL);
    result = SSLSetCertificate(context, certificates);
    if (result)
    {
        fprintf(stderr, "Error setting the client certificate ");
        return NO;
    }

    CFRelease(certificates);

    // Perform SSL handshake.
    do {result = SSLHandshake(context);}
        while(result == errSSLWouldBlock);


    // Convert string into device token data.
    NSMutableData *deviceToken = [NSMutableData data];
    unsigned value;
    NSScanner *scanner = [NSScanner
        scannerWithString:self.deviceTokenID];
    while(![scanner isAtEnd]) {
        [scanner scanHexInt:&value];
        value = htonl(value);
        [deviceToken appendBytes:&value length:sizeof(value)];
    }

    // Create C input variables.
    char *deviceTokenBinary = (char *)deviceToken.bytes;
    char *payloadBinary = (char *)[payload UTF8String];
    size_t payloadLength = strlen(payloadBinary);

    // Prepare message
    uint8_t command = 0;
    char message[293];
    char *pointer = message;
    uint16_t networkTokenLength = htons(32);
    uint16_t networkPayloadLength = htons(payloadLength);

    // Compose message.
    memcpy(pointer, &command, sizeof(uint8_t));
    pointer += sizeof(uint8_t);
    memcpy(pointer, &networkTokenLength, sizeof(uint16_t));
    pointer += sizeof(uint16_t);
    memcpy(pointer, deviceTokenBinary, 32);
    pointer += 32;
    memcpy(pointer, &networkPayloadLength, sizeof(uint16_t));
    pointer += sizeof(uint16_t);
    memcpy(pointer, payloadBinary, payloadLength);
    pointer += payloadLength;

    // Send message over SSL.
    size_t processed = 0;
    result = SSLWrite(context, &message, (pointer - message),
        &processed);
    if (result)
    {
        fprintf(stderr, "Error sending message via SSL. ");
        return NO;
    }
    else
    {
        printf("Message sent. ");
        return YES;
    }
}



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Advanced-Cookbook and go to the folder for Chapter 13.


Feedback Service

Apps don’t live forever. Users add, remove, and replace applications on their devices all the time. From an APNS point of view, it’s pointless to deliver notifications to devices that no longer host your application. As a push provider, it’s your duty to remove inactive device tokens from your active support list. As Apple puts it, “APNS monitors providers for their diligence in checking the feedback service and refraining from sending push notifications to nonexistent applications on devices.” Big Brother is watching.

Apple provides a simple way to manage inactive device tokens. When users uninstall apps from a device, push notifications begin to fail. Apple tracks these failures and provides reports from its APNS feedback server. The APNS feedback service lists devices that failed to receive notifications. As a provider, you need to fetch this report on a periodic basis and weed through your device tokens.

The feedback server hosts sandbox and production addresses, just like the notification server. You find these at feedback.push.apple.com (port 2196) and feedback.sandbox.push.apple.com (port 2196). You contact the server with a production SSL certificate and shake hands in the same way you do to send notifications. After the handshake, read your results. The server sends data immediately without any further explicit commands on your side.

The feedback data consists of 38 bytes. This includes the time (4 bytes), the token length (2 bytes), and the token itself (32 bytes). The timestamp tells you when APNS first determined that the application no longer existed on the device. This uses a standard UNIX epoch, namely seconds since Midnight, January 1, 1970. The device token is stored in binary format. You need to convert it to a hex representation to match it to your device tokens if you use strings to store token data. At the time of writing this book, you can ignore the length bytes. They are always 0 and 32, referring to the 32-byte length of the device token:

// Retrieve message from SSL.
size_t processed = 0;
char buffer[38];
do
{
    // Fetch the next item
    result = SSLRead(context, buffer, 38, &processed);
    if (result) break;

    // Recover Date from data
    char *b = buffer;
    NSTimeInterval ti = ((unsigned char)b[0] << 24) +
       ((unsigned char)b[1] << 16) +
       ((unsigned char)b[2] << 8) +
       (unsigned char)b[3];
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:ti];

    // Recover Device ID
    NSMutableString *deviceID = [NSMutableString string];
    b += 6;
    for (int i = 0; i < 32; i++) [
        deviceID appendFormat:@"%02x", (unsigned char)b[i]];

    // Add dictionary to results
    [results addObject:
        [NSDictionary dictionaryWithObject:date
        forKey:deviceID]];

} while (processed > 0);


Note

Search your Xcode Organizer Console for “aps” to locate APNS error messages.


Designing for Push

When designing for push, keep scaling in mind. Normal computing doesn’t need to scale. When coding is done, an app runs on a device using the local CPU. Should a developer deploy an extra 10,000 copies, there’s no further investment involved other than increased technical support.

Push computing does scale. Whether you have 10,000 or 100,000 or 1,000,000 users matters. That’s because developers must provide the service layer that handles the operations for every unit sold. The more users supported, the greater the costs will be. Consider that these services need to be completely reliable and that consumers will not be tolerant of extended downtimes.

On top of reliability, add in security concerns. Many polled services require secure credentials. Those credentials must be uploaded to the service for remote use rather than being stored solely on the device. Even if the service in question does not use that kind of authentication, the device token that allows your service to contact a specific device is sensitive in itself. Should that identifier be stolen, it could let spammers send unsolicited alerts. Any developer who enters this arena must take these possible threats seriously and provide secure solutions for storing and protecting information.

Third-party provider Urban Airship (urbanairship.com) offers ready-to-use push infrastructure, which is widely used in the iOS developer community. Newcomer Parse (parse.com), an up-and-coming competitor, simplifies push composition and deployment as well.

Summary

In this chapter, you saw push notifications both from a client-building point of view and as a provider. You learned about the kinds of notifications you can send and how to create the payload that moves those notifications to the device. You discovered registering and unregistering devices and how users can opt in and out from the service.

Much of the push story lies outside this chapter. It’s up to you to set up a server and deal with security, bandwidth, and scaling issues. The reality of deployment is that there are many platforms and languages that you can use that go beyond the Objective-C sample code shown here, typically via Ruby, Python, and PHP. Regardless, the concepts discussed and recipes shown in this chapter give you a good stepping-off point. You know what the issues are and how things must work. Now it’s up to you to put them to good use:

• The big wins of notifications are their instant updates and immediate presentation. Like SMS messages, they’re hard to overlook when they arrive on your device. There’s nothing wrong in opting out of push if your application does not demand that kind of immediacy.

• Guard your SSL certificate and device tokens. It varies how Apple will respond to security breaches, but experience suggests that it will be messy and unpleasant.

• Don’t leave users without service when you have promised to provide it to them. Build a timeline into your business plan that anticipates what it will take to keep delivering notifications over time and how you will fund this. Consumers will not be tolerant of extended downtimes; your service must be reliable.

• Apple provides a verbose logging tool for push notification, which you can read about at its Developer Forums site.

• Don’t spam users. Push notifications are not meant to sell products or promote sales. Respect your user base.

• Build to scale. Although your application may not initially have tens of thousands of users, anticipate a successful app launch as well as a modest one. Create a system that can grow along with your user base.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.174.248