Sending Push Notifications

Now that CocoaServer has the device token of its client application, it can send a notification package to Apple’s push notification server. The first step in this process is connecting to the notification server with NSStream. Once we have a connection, we’ll pack up and send the notification data that will eventually be sent to the device.

Connecting to Apple’s server with NSStream

Apple requires that the data sent to its server from the provider be encrypted. This is where the SSL certificate you downloaded comes into play – the provider must encrypt its outgoing data with this certificate.

Locate the aps_developer_identity.cer file that you downloaded earlier. Double-click on it to add it to your Login keychain. In Keychain Access, you should see this certificate and its private key (Figure 29.6). Remember that the certificate that signs your application for deployment is different than the certificate used to encrypt data.

Figure 29.6  SSL certificate in Keychain Access

SSL certificate in Keychain Access

To make it easy for CocoaServer to find this certificate, you will bundle it with the application. Drag aps_developer_identity.cer to CocoaServer’s project navigator and check the box to copy it to the project’s directory. Now, when CocoaServer is built, it will have access to this certificate.

Using NSStream

The provider will make a connection to Apple’s push notification server and secure the data it sends by signing it with this certificate. This connection to the notification server should stay open even if you aren’t currently sending push notifications; if you close and re-open the connection multiple times, Apple may think you are trying to flood the server and reject your connection. Therefore, when CocoaServer starts, you will initiate a connection to the notification server and keep it open.

In CocoaServerAppDelegate.h, declare three new methods and two new instance variables.

 ​ ​ ​ ​N​S​O​u​t​p​u​t​S​t​r​e​a​m​ ​*​w​r​i​t​e​S​t​r​e​a​m​;​
 ​ ​ ​ ​N​S​I​n​p​u​t​S​t​r​e​a​m​ ​*​r​e​a​d​S​t​r​e​a​m​;​
}​
-​ ​(​v​o​i​d​)​c​o​n​f​i​g​u​r​e​S​t​r​e​a​m​s​;​
-​ ​(​N​S​A​r​r​a​y​ ​*​)​c​e​r​t​i​f​i​c​a​t​e​A​r​r​a​y​;​
-​ ​(​v​o​i​d​)​c​o​n​n​e​c​t​T​o​N​o​t​i​f​i​c​a​t​i​o​n​S​e​r​v​e​r​;​

The implementation of connectToNotificationServer will open a streaming connection to the notification server. A streaming connection is kept alive, and the two connected machines are free to send data to each other whenever they please.

Let’s get some network theory out of the way. In Unix (on which iOS is built), we use file descriptors to transfer data. We can think of a file descriptor as a wormhole. We put data into it, and that data appears at the other end. Data arrives the same way. The operating system handles where that data goes to or comes from; we just interface with our end of the file descriptor. The other side of a file descriptor may be connected to a file on the filesystem or to the network hardware that talks to the Internet. This is low-level stuff, and a number of abstractions have been built on top of it to make our lives easier. You don’t have to know about file descriptors to load a file, but that’s what is happening underneath the hood.

When a file descriptor is used to channel data to and from the network hardware, we call it an internet socket (or more commonly, just a socket). When you put data into an internet socket, it ends up on another machine. When that machine sends back data, you can read that data from a socket. Thus, a streaming connection is really just two sockets: one that you put data on and one that you take data from.

In connectToNotificationServer, you’re going to create your sockets and connect them using the C-level Core Foundation framework. The function CFStreamCreatePairWithSocketToHost returns two stream objects of type CFWriteStreamRef and CFReadStreamRef.

These Core Foundation classes are toll-free bridged with the two classes from Apple’s streaming API: NSOutputStream and NSInputStream. You will use instances of these classes to manage your connected sockets.

In CocoaServerAppDelegate.m, implement connectToNotificationServer.

-​ ​(​v​o​i​d​)​c​o​n​n​e​c​t​T​o​N​o​t​i​f​i​c​a​t​i​o​n​S​e​r​v​e​r​
{​
 ​ ​ ​ ​/​/​ ​C​o​n​n​e​c​t​ ​t​o​ ​p​u​s​h​ ​n​o​t​i​f​i​c​a​t​i​o​n​ ​s​e​r​v​e​r​,​ ​w​e​ ​g​e​t​ ​b​a​c​k​ ​t​w​o​ ​s​t​r​e​a​m​ ​o​b​j​e​c​t​s​
 ​ ​ ​ ​/​/​ ​t​h​a​t​ ​w​i​l​l​ ​a​l​l​o​w​ ​u​s​ ​t​o​ ​w​r​i​t​e​ ​t​o​ ​a​n​d​ ​r​e​a​d​ ​f​r​o​m​ ​t​h​a​t​ ​s​e​r​v​e​r​
 ​ ​ ​ ​C​F​S​t​r​e​a​m​C​r​e​a​t​e​P​a​i​r​W​i​t​h​S​o​c​k​e​t​T​o​H​o​s​t​(​N​U​L​L​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​(​C​F​S​t​r​i​n​g​R​e​f​)​@​"​g​a​t​e​w​a​y​.​s​a​n​d​b​o​x​.​p​u​s​h​.​a​p​p​l​e​.​c​o​m​"​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​2​1​9​5​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​(​C​F​R​e​a​d​S​t​r​e​a​m​R​e​f​ ​*​)​(​&​r​e​a​d​S​t​r​e​a​m​)​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​(​C​F​W​r​i​t​e​S​t​r​e​a​m​R​e​f​ ​*​)​(​&​w​r​i​t​e​S​t​r​e​a​m​)​)​;​
 ​ ​ ​ ​/​/​ ​O​p​e​n​ ​u​p​ ​t​h​e​ ​s​t​r​e​a​m​s​
 ​ ​ ​ ​[​r​e​a​d​S​t​r​e​a​m​ ​o​p​e​n​]​;​
 ​ ​ ​ ​[​w​r​i​t​e​S​t​r​e​a​m​ ​o​p​e​n​]​;​

 ​ ​ ​ ​/​/​ ​M​a​k​e​ ​s​u​r​e​ ​t​h​a​t​ ​o​p​e​n​i​n​g​ ​d​i​d​n​'​t​ ​f​a​i​l​
 ​ ​ ​ ​i​f​ ​(​[​r​e​a​d​S​t​r​e​a​m​ ​s​t​r​e​a​m​S​t​a​t​u​s​]​ ​!​=​ ​N​S​S​t​r​e​a​m​S​t​a​t​u​s​E​r​r​o​r​
 ​ ​ ​ ​&​&​ ​[​w​r​i​t​e​S​t​r​e​a​m​ ​s​t​r​e​a​m​S​t​a​t​u​s​]​ ​!​=​ ​N​S​S​t​r​e​a​m​S​t​a​t​u​s​E​r​r​o​r​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​c​o​n​f​i​g​u​r​e​S​t​r​e​a​m​s​]​;​
 ​ ​ ​ ​}​
 ​ ​ ​ ​e​l​s​e​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​F​a​i​l​e​d​ ​t​o​ ​c​o​n​n​e​c​t​ ​t​o​ ​A​p​p​l​e​.​"​)​;​
 ​ ​ ​ ​}​
}​

You can see in the CFStreamCreatePairWithSocketToHost function that the host name of the development push notification server is gateway.sandbox.push.apple.com and it accepts connections on port 2195.

Since these streams need to encrypt their data, you must load the certificate into memory and hand it to them. First, add Security.framework to your project. Then, at the top of CocoaServerAppDelegate.m, import the top-level header for this framework.

#​i​m​p​o​r​t​ ​<​S​e​c​u​r​i​t​y​/​S​e​c​u​r​i​t​y​.​h​>​

Next, in CocoaServerAppDelegate.m, implement certificateArray to load the certificate from the bundle, establish its private key (also called its identity) from the Keychain, and then return an array containing the certificate and its key.

-​ ​(​N​S​A​r​r​a​y​ ​*​)​c​e​r​t​i​f​i​c​a​t​e​A​r​r​a​y​
{​
 ​ ​ ​ ​/​/​ ​G​e​t​ ​t​h​e​ ​p​a​t​h​ ​o​f​ ​t​h​e​ ​c​e​r​t​i​f​i​c​a​t​e​ ​i​n​ ​t​h​e​ ​b​u​n​d​l​e​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​c​e​r​t​P​a​t​h​ ​=​
 ​ ​ ​ ​ ​ ​ ​ ​[​[​N​S​B​u​n​d​l​e​ ​m​a​i​n​B​u​n​d​l​e​]​ ​p​a​t​h​F​o​r​R​e​s​o​u​r​c​e​:​@​"​a​p​s​_​d​e​v​e​l​o​p​e​r​_​i​d​e​n​t​i​t​y​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​o​f​T​y​p​e​:​@​"​c​e​r​"​]​;​

 ​ ​ ​ ​/​/​ ​P​u​l​l​ ​t​h​e​ ​d​a​t​a​ ​f​r​o​m​ ​t​h​e​ ​f​i​l​e​s​y​s​t​e​m​ ​a​n​d​ ​c​r​e​a​t​e​ ​a​ ​S​e​c​C​e​r​t​i​f​i​c​a​t​e​ ​o​b​j​e​c​t​
 ​ ​ ​ ​N​S​D​a​t​a​ ​*​c​e​r​t​D​a​t​a​ ​=​ ​[​N​S​D​a​t​a​ ​d​a​t​a​W​i​t​h​C​o​n​t​e​n​t​s​O​f​F​i​l​e​:​c​e​r​t​P​a​t​h​]​;​
 ​ ​ ​ ​S​e​c​C​e​r​t​i​f​i​c​a​t​e​R​e​f​ ​c​e​r​t​ ​=​ ​S​e​c​C​e​r​t​i​f​i​c​a​t​e​C​r​e​a​t​e​W​i​t​h​D​a​t​a​(​N​U​L​L​,​ ​(​C​F​D​a​t​a​R​e​f​)​c​e​r​t​D​a​t​a​)​;​

 ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​t​h​e​ ​i​d​e​n​t​i​t​y​ ​(​p​r​i​v​a​t​e​ ​k​e​y​)​ ​w​h​i​c​h​ ​r​e​q​u​i​r​e​s​
 ​ ​ ​ ​/​/​ ​t​h​a​t​ ​t​h​e​ ​c​e​r​t​i​f​i​c​a​t​e​ ​l​i​v​e​s​ ​i​n​ ​t​h​e​ ​k​e​y​c​h​a​i​n​
 ​ ​ ​ ​S​e​c​I​d​e​n​t​i​t​y​R​e​f​ ​i​d​e​n​t​i​t​y​;​
 ​ ​ ​ ​O​S​S​t​a​t​u​s​ ​e​r​r​ ​=​ ​S​e​c​I​d​e​n​t​i​t​y​C​r​e​a​t​e​W​i​t​h​C​e​r​t​i​f​i​c​a​t​e​(​N​U​L​L​,​ ​c​e​r​t​,​ ​&​i​d​e​n​t​i​t​y​)​;​
 ​ ​ ​ ​i​f​ ​(​e​r​r​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​F​a​i​l​e​d​ ​t​o​ ​c​r​e​a​t​e​ ​c​e​r​t​i​f​i​c​a​t​e​ ​i​d​e​n​t​i​t​y​:​ ​%​d​"​,​ ​e​r​r​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​r​e​t​u​r​n​ ​n​i​l​;​
 ​ ​ ​ ​}​

 ​ ​ ​ ​/​/​ ​P​u​t​ ​t​h​e​ ​k​e​y​ ​a​n​d​ ​c​e​r​t​i​f​i​c​a​t​e​ ​i​n​t​o​ ​a​n​ ​a​r​r​a​y​
 ​ ​ ​ ​r​e​t​u​r​n​ ​[​N​S​A​r​r​a​y​ ​a​r​r​a​y​W​i​t​h​O​b​j​e​c​t​s​:​(​i​d​)​i​d​e​n​t​i​t​y​,​ ​(​i​d​)​c​e​r​t​,​ ​n​i​l​]​;​
}​

Figure 29.7  Stream behavior

Stream behavior

Now, the implementation of configureStreams will configure the streams with the encryption certificate. In the same method, you will give the streams delegates and schedule them into the run loop so that they are monitored for data (Figure 29.7). Implement this method in CocoaServerAppDelegate.m.

-​ ​(​v​o​i​d​)​c​o​n​f​i​g​u​r​e​S​t​r​e​a​m​s​
{​
 ​ ​ ​ ​N​S​A​r​r​a​y​ ​*​c​e​r​t​A​r​r​a​y​ ​=​ ​[​s​e​l​f​ ​c​e​r​t​i​f​i​c​a​t​e​A​r​r​a​y​]​;​
 ​ ​ ​ ​i​f​(​!​c​e​r​t​A​r​r​a​y​)​
 ​ ​ ​ ​ ​ ​ ​ ​r​e​t​u​r​n​;​

 ​ ​ ​ ​/​/​ ​G​i​v​e​ ​t​h​e​ ​s​t​r​e​a​m​s​ ​t​h​e​i​r​ ​S​S​L​ ​s​e​t​t​i​n​g​s​ ​s​o​ ​t​h​e​y​ ​c​a​n​ ​e​n​c​r​y​p​t​/​d​e​c​r​y​p​t​
 ​ ​ ​ ​/​/​ ​d​a​t​a​ ​t​o​/​f​r​o​m​ ​t​h​e​ ​s​e​r​v​e​r​
 ​ ​ ​ ​N​S​D​i​c​t​i​o​n​a​r​y​ ​*​s​s​l​S​e​t​t​i​n​g​s​ ​=​
 ​ ​ ​ ​ ​ ​ ​ ​[​N​S​D​i​c​t​i​o​n​a​r​y​ ​d​i​c​t​i​o​n​a​r​y​W​i​t​h​O​b​j​e​c​t​s​A​n​d​K​e​y​s​:​[​s​e​l​f​ ​c​e​r​t​i​f​i​c​a​t​e​A​r​r​a​y​]​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​(​i​d​)​k​C​F​S​t​r​e​a​m​S​S​L​C​e​r​t​i​f​i​c​a​t​e​s​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​(​i​d​)​k​C​F​S​t​r​e​a​m​S​o​c​k​e​t​S​e​c​u​r​i​t​y​L​e​v​e​l​N​e​g​o​t​i​a​t​e​d​S​S​L​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​(​i​d​)​k​C​F​S​t​r​e​a​m​S​S​L​L​e​v​e​l​,​ ​n​i​l​]​;​

 ​ ​ ​ ​[​w​r​i​t​e​S​t​r​e​a​m​ ​s​e​t​P​r​o​p​e​r​t​y​:​s​s​l​S​e​t​t​i​n​g​s​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​o​r​K​e​y​:​(​i​d​)​k​C​F​S​t​r​e​a​m​P​r​o​p​e​r​t​y​S​S​L​S​e​t​t​i​n​g​s​]​;​

 ​ ​ ​ ​[​r​e​a​d​S​t​r​e​a​m​ ​s​e​t​P​r​o​p​e​r​t​y​:​s​s​l​S​e​t​t​i​n​g​s​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​o​r​K​e​y​:​(​i​d​)​k​C​F​S​t​r​e​a​m​P​r​o​p​e​r​t​y​S​S​L​S​e​t​t​i​n​g​s​]​;​

 ​ ​ ​ ​/​/​ ​G​i​v​e​ ​s​t​r​e​a​m​s​ ​a​ ​d​e​l​e​g​a​t​e​ ​s​o​ ​w​e​ ​c​a​n​ ​m​o​n​i​t​o​r​ ​t​h​e​m​
 ​ ​ ​ ​[​r​e​a​d​S​t​r​e​a​m​ ​s​e​t​D​e​l​e​g​a​t​e​:​s​e​l​f​]​;​
 ​ ​ ​ ​[​w​r​i​t​e​S​t​r​e​a​m​ ​s​e​t​D​e​l​e​g​a​t​e​:​s​e​l​f​]​;​

 ​ ​ ​ ​/​/​ ​S​c​h​e​d​u​l​e​ ​t​h​e​ ​s​t​r​e​a​m​s​ ​i​n​t​o​ ​t​h​e​ ​r​u​n​ ​l​o​o​p​ ​s​o​ ​t​h​a​t​ ​t​h​e​y​ ​c​a​n​ ​d​o​ ​t​h​e​i​r​ ​w​o​r​k​
 ​ ​ ​ ​[​w​r​i​t​e​S​t​r​e​a​m​ ​s​c​h​e​d​u​l​e​I​n​R​u​n​L​o​o​p​:​[​N​S​R​u​n​L​o​o​p​ ​c​u​r​r​e​n​t​R​u​n​L​o​o​p​]​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​o​r​M​o​d​e​:​N​S​D​e​f​a​u​l​t​R​u​n​L​o​o​p​M​o​d​e​]​;​
 ​ ​ ​ ​[​r​e​a​d​S​t​r​e​a​m​ ​s​c​h​e​d​u​l​e​I​n​R​u​n​L​o​o​p​:​[​N​S​R​u​n​L​o​o​p​ ​c​u​r​r​e​n​t​R​u​n​L​o​o​p​]​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​o​r​M​o​d​e​:​N​S​D​e​f​a​u​l​t​R​u​n​L​o​o​p​M​o​d​e​]​;​
}​

Once the streams have been opened and scheduled into the run loop, they will start telling their delegate (the CocoaServerAppDelegate) what’s going on in their lives. For example, a read stream might say, I have some new data sitting on the socket. Would you like to read it? A write stream might say, I just sent a bunch of data, and now I’m ready for more. Also, if a stream encounters a problem, it will report an error to its delegate.

In CocoaServerAppDelegate.h, declare that CocoaServerAppDelegate conforms to the NSStreamDelegate protocol.

@​i​n​t​e​r​f​a​c​e​ ​C​o​c​o​a​S​e​r​v​e​r​A​p​p​D​e​l​e​g​a​t​e​ ​:​ ​N​S​O​b​j​e​c​t​
 ​ ​ ​ ​<​N​S​A​p​p​l​i​c​a​t​i​o​n​D​e​l​e​g​a​t​e​,​ ​N​S​N​e​t​S​e​r​v​i​c​e​D​e​l​e​g​a​t​e​,​
 ​ ​ ​ ​N​S​T​a​b​l​e​V​i​e​w​D​a​t​a​S​o​u​r​c​e​,​ ​N​S​T​a​b​l​e​V​i​e​w​D​e​l​e​g​a​t​e​,​
 ​ ​ ​ ​N​S​S​t​r​e​a​m​D​e​l​e​g​a​t​e​>​

Then, in CocoaServerAppDelegate.m, implement the event-handling delegate method.

-​ ​(​v​o​i​d​)​s​t​r​e​a​m​:​(​N​S​S​t​r​e​a​m​ ​*​)​a​S​t​r​e​a​m​ ​h​a​n​d​l​e​E​v​e​n​t​:​(​N​S​S​t​r​e​a​m​E​v​e​n​t​)​e​v​e​n​t​C​o​d​e​
{​
 ​ ​ ​ ​s​w​i​t​c​h​(​e​v​e​n​t​C​o​d​e​)​
 ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​c​a​s​e​ ​N​S​S​t​r​e​a​m​E​v​e​n​t​H​a​s​B​y​t​e​s​A​v​a​i​l​a​b​l​e​:​
 ​ ​ ​ ​ ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​%​@​ ​h​a​s​ ​b​y​t​e​s​"​,​ ​a​S​t​r​e​a​m​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​}​ ​b​r​e​a​k​;​
 ​ ​ ​ ​ ​ ​ ​ ​c​a​s​e​ ​N​S​S​t​r​e​a​m​E​v​e​n​t​O​p​e​n​C​o​m​p​l​e​t​e​d​:​
 ​ ​ ​ ​ ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​%​@​ ​i​s​ ​o​p​e​n​"​,​ ​a​S​t​r​e​a​m​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​}​ ​b​r​e​a​k​;​
 ​ ​ ​ ​ ​ ​ ​ ​c​a​s​e​ ​N​S​S​t​r​e​a​m​E​v​e​n​t​H​a​s​S​p​a​c​e​A​v​a​i​l​a​b​l​e​:​
 ​ ​ ​ ​ ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​%​@​ ​c​a​n​ ​a​c​c​e​p​t​ ​b​y​t​e​s​"​,​ ​a​S​t​r​e​a​m​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​}​b​r​e​a​k​;​
 ​ ​ ​ ​ ​ ​ ​ ​c​a​s​e​ ​N​S​S​t​r​e​a​m​E​v​e​n​t​E​r​r​o​r​O​c​c​u​r​r​e​d​:​
 ​ ​ ​ ​ ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​%​@​ ​e​r​r​o​r​:​ ​%​@​"​,​ ​a​S​t​r​e​a​m​,​ ​[​a​S​t​r​e​a​m​ ​s​t​r​e​a​m​E​r​r​o​r​]​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​}​ ​b​r​e​a​k​;​
 ​ ​ ​ ​ ​ ​ ​ ​c​a​s​e​ ​N​S​S​t​r​e​a​m​E​v​e​n​t​E​n​d​E​n​c​o​u​n​t​e​r​e​d​:​
 ​ ​ ​ ​ ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​%​@​ ​e​n​d​e​d​ ​-​ ​p​r​o​b​a​b​l​y​ ​c​l​o​s​e​d​ ​b​y​ ​s​e​r​v​e​r​"​,​ ​a​S​t​r​e​a​m​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​}​ ​b​r​e​a​k​;​
 ​ ​ ​ ​}​
}​

Finally, at the end of applicationDidFinishLaunching:, kick off the whole process of connecting to the Apple push notification server.

 ​ ​ ​ ​s​e​r​v​i​c​e​ ​=​ ​[​[​N​S​N​e​t​S​e​r​v​i​c​e​ ​a​l​l​o​c​]​ ​i​n​i​t​W​i​t​h​D​o​m​a​i​n​:​@​"​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​t​y​p​e​:​@​"​_​h​t​t​p​.​_​t​c​p​.​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​n​a​m​e​:​@​"​C​o​c​o​a​H​T​T​P​S​e​r​v​e​r​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​p​o​r​t​:​[​s​e​r​v​e​r​ ​p​o​r​t​]​]​;​
 ​ ​ ​ ​[​s​e​r​v​i​c​e​ ​s​e​t​D​e​l​e​g​a​t​e​:​s​e​l​f​]​;​
 ​ ​ ​ ​[​s​e​r​v​i​c​e​ ​p​u​b​l​i​s​h​]​;​

 ​ ​ ​ ​[​s​e​l​f​ ​c​o​n​n​e​c​t​T​o​N​o​t​i​f​i​c​a​t​i​o​n​S​e​r​v​e​r​]​;​
}​

Build and run CocoaServer. Mac OS X will ask if it is okay to use the private key to securely speak with the notification server. Select Always Allow. After a moment, your console should report that both streams have opened and that the write stream can accept bytes. The console will look something like this:

<​S​S​C​F​I​n​p​u​t​S​t​r​e​a​m​:​ ​0​x​1​0​0​4​4​d​5​8​0​>​ ​i​s​ ​o​p​e​n​
<​N​S​C​F​O​u​t​p​u​t​S​t​r​e​a​m​:​ ​0​x​1​0​0​4​4​d​6​5​0​>​ ​i​s​ ​o​p​e​n​
<​N​S​C​F​O​u​t​p​u​t​S​t​r​e​a​m​:​ ​0​x​1​0​0​4​4​d​6​5​0​>​ ​c​a​n​ ​a​c​c​e​p​t​ ​b​y​t​e​s​

You now have an encrypted connection to the push notification server. In the next section, you’ll format and send the notification data.

Providing data to the notification server

Once you have a connection to the notification server, you can send it notifications to forward to devices. There are two formats for the notification data sent to the server: simple and enhanced. We will use the enhanced format because it can inform the provider of any error in the data and it allows the provider to include an expiration date for the notification.

The data will be a packed binary buffer, so we will use an instance of NSMutableData to put together this information (Figure 29.8). The first byte of the data is called the command and it is either 1 or 0. If the notification data is in the enhanced format, the command is 1, otherwise, it’s 0.

Figure 29.8  Enhanced notification format

Enhanced notification format

Next is a 32-bit value that identifies this notification. If there is an error delivering the notification, your application will be given back this identifier along with the error so you can decide what to do next.

Another 32-bit integer follows the identifier, and it specifies an expiration date. If a push notification cannot be delivered to a device (most likely because it does not have an Internet connection), the notification server will try to re-send it later. The expiration date says, If you still haven’t delivered this notification by this time, then just trash it. The value of expiration date is the number of seconds past the epoch (Jan 1, 1970) at which the server should trash the notification. If you wanted, for example, to have the notification expire in a day, you will call the function time to get the current number of seconds since the epoch and then add a day’s worth of seconds to that value.

Next comes the device token: first a 16-bit integer to specify the length of the device token data (which at this point is always 32 bytes, but in the future may vary) and then the token data itself. The token data information is the same value the iOS application receives after it registers. It allows the push notification server to route the notification to the correct device.

Last is the actual notification payload (and a 16-bit integer before it to specify its size). The payload is the notification data that will make it to the device. It can include alert text, the name of a sound file to play, or a badge number to place on the application’s icon. This data will be binary-encoded JSON. JSON is like XML; it is a platform-independent mark-up language used to transport data. There is no built-in JSON generator in the iOS SDK, but constructing it by hand for simple things like delivering a notification is easy enough.

In the JSON payload, there are a few reserved keys. The first is aps, which is the top-level container for standard notification key-values. The standard notification keys are alert, sound, and badge. Therefore, a simple notification that sends an alert to a device looks like this:

{​
 ​ ​ ​ ​"​a​p​s​"​:​
 ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​"​a​l​e​r​t​"​:​"​H​e​r​e​'​s​ ​a​ ​s​i​m​p​l​e​ ​m​e​s​s​a​g​e​"​
 ​ ​ ​ ​}​
}​

If you want to include a sound file to play with the alert, you would add the name of that file to the aps container.

{​
 ​ ​ ​ ​"​a​p​s​"​:​
 ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​"​a​l​e​r​t​"​:​"​H​e​r​e​'​s​ ​a​ ​s​i​m​p​l​e​ ​m​e​s​s​a​g​e​"​,​
 ​ ​ ​ ​ ​ ​ ​ ​"​s​o​u​n​d​"​:​"​S​o​u​n​d​1​2​.​a​i​f​"​
 ​ ​ ​ ​}​
}​

The alert can also be a container if you need to add supported customizations to your notification, like localizing the text or providing a special launch image for a notified application. You can check the documentation for all of these keys. Furthermore, you can specify your own keys outside of the aps container for use by your application. These keys will be available to the iOS application when it is awoken by a notification, but they will not be visible to the user.

However, you should never rely on the delivery of a push notification. Therefore, do not relay critical information in customizations of a notification; instead, indicate to the application that it should fetch critical information from the server. Also, do not include confidential information, like passwords or credit card numbers, in the payload.

Time to make the data. In CocoaServerAppDelegate.h, declare a new method to construct the notification data.

-​ ​(​N​S​D​a​t​a​ ​*​)​n​o​t​i​f​i​c​a​t​i​o​n​D​a​t​a​F​o​r​M​e​s​s​a​g​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​m​s​g​T​e​x​t​ ​t​o​k​e​n​:​(​N​S​D​a​t​a​ ​*​)​t​o​k​e​n​;​

Implement this method in CocoaServerAppDelegate.m.

-​ ​(​N​S​D​a​t​a​ ​*​)​n​o​t​i​f​i​c​a​t​i​o​n​D​a​t​a​F​o​r​M​e​s​s​a​g​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​m​s​g​T​e​x​t​ ​t​o​k​e​n​:​(​N​S​D​a​t​a​ ​*​)​t​o​k​e​n​
{​

 ​ ​ ​ ​/​/​ ​T​o​ ​s​i​g​n​i​f​y​ ​t​h​e​ ​e​n​h​a​n​c​e​d​ ​f​o​r​m​a​t​,​ ​w​e​ ​u​s​e​ ​1​ ​a​s​ ​t​h​e​ ​f​i​r​s​t​ ​b​y​t​e​
 ​ ​ ​ ​u​i​n​t​8​_​t​ ​c​o​m​m​a​n​d​ ​=​ ​1​;​

 ​ ​ ​ ​/​/​ ​T​h​i​s​ ​i​s​ ​t​h​e​ ​i​d​e​n​t​i​f​i​e​r​ ​f​o​r​ ​t​h​i​s​ ​s​p​e​c​i​f​i​c​ ​n​o​t​i​f​i​c​a​t​i​o​n​
 ​ ​ ​ ​s​t​a​t​i​c​ ​u​i​n​t​3​2​_​t​ ​i​d​e​n​t​i​f​i​e​r​ ​=​ ​5​0​0​0​;​

 ​ ​ ​ ​/​/​ ​T​h​e​ ​n​o​t​i​f​i​c​a​t​i​o​n​ ​w​i​l​l​ ​e​x​p​i​r​e​ ​i​n​ ​o​n​e​ ​d​a​y​
 ​ ​ ​ ​u​i​n​t​3​2​_​t​ ​e​x​p​i​r​y​ ​=​ ​h​t​o​n​l​(​t​i​m​e​(​N​U​L​L​)​ ​+​ ​8​6​4​0​0​)​;​

 ​ ​ ​ ​/​/​ ​F​i​n​d​ ​t​h​e​ ​b​i​n​a​r​y​ ​l​e​n​g​t​h​s​ ​o​f​ ​t​h​e​ ​d​a​t​a​ ​w​e​ ​w​i​l​l​ ​s​e​n​d​
 ​ ​ ​ ​u​i​n​t​1​6​_​t​ ​t​o​k​e​n​L​e​n​g​t​h​ ​=​ ​h​t​o​n​s​(​[​t​o​k​e​n​ ​l​e​n​g​t​h​]​)​;​

 ​ ​ ​ ​/​/​ ​M​u​s​t​ ​e​s​c​a​p​e​ ​t​e​x​t​ ​b​e​f​o​r​e​ ​i​n​s​e​r​t​i​n​g​ ​i​n​ ​J​S​O​N​
 ​ ​ ​ ​N​S​M​u​t​a​b​l​e​S​t​r​i​n​g​ ​*​e​s​c​a​p​e​d​T​e​x​t​ ​=​ ​[​[​m​s​g​T​e​x​t​ ​m​u​t​a​b​l​e​C​o​p​y​]​ ​a​u​t​o​r​e​l​e​a​s​e​]​;​

 ​ ​ ​ ​/​/​ ​R​e​p​l​a​c​e​ ​​ ​w​i​t​h​ ​​​
 ​ ​ ​ ​[​e​s​c​a​p​e​d​T​e​x​t​ ​r​e​p​l​a​c​e​O​c​c​u​r​r​e​n​c​e​s​O​f​S​t​r​i​n​g​:​@​"​​​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​w​i​t​h​S​t​r​i​n​g​:​@​"​​​​​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​o​p​t​i​o​n​s​:​0​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​r​a​n​g​e​:​N​S​M​a​k​e​R​a​n​g​e​(​0​,​ ​[​e​s​c​a​p​e​d​T​e​x​t​ ​l​e​n​g​t​h​]​)​]​;​

 ​ ​ ​ ​/​/​ ​R​e​p​l​a​c​e​ ​"​ ​w​i​t​h​ ​​"​
 ​ ​ ​ ​[​e​s​c​a​p​e​d​T​e​x​t​ ​r​e​p​l​a​c​e​O​c​c​u​r​r​e​n​c​e​s​O​f​S​t​r​i​n​g​:​@​"​​"​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​w​i​t​h​S​t​r​i​n​g​:​@​"​​​​"​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​o​p​t​i​o​n​s​:​0​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​r​a​n​g​e​:​N​S​M​a​k​e​R​a​n​g​e​(​0​,​ ​[​e​s​c​a​p​e​d​T​e​x​t​ ​l​e​n​g​t​h​]​)​]​;​

 ​ ​ ​ ​/​/​ ​C​o​n​s​t​r​u​c​t​ ​t​h​e​ ​J​S​O​N​ ​p​a​y​l​o​a​d​ ​t​o​ ​d​e​l​i​v​e​r​ ​t​o​ ​t​h​e​ ​d​e​v​i​c​e​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​p​a​y​l​o​a​d​ ​=​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​N​S​S​t​r​i​n​g​ ​s​t​r​i​n​g​W​i​t​h​F​o​r​m​a​t​:​@​"​{​​"​a​p​s​​"​:​{​​"​a​l​e​r​t​​"​:​​"​%​@​​"​}​}​"​,​ ​e​s​c​a​p​e​d​T​e​x​t​]​;​

 ​ ​ ​ ​/​/​ ​W​e​'​l​l​ ​h​a​v​e​ ​t​o​ ​e​n​c​o​d​e​ ​t​h​i​s​ ​i​n​t​o​ ​a​ ​b​i​n​a​r​y​ ​b​u​f​f​e​r​,​ ​s​o​ ​N​S​S​t​r​i​n​g​ ​w​o​n​'​t​ ​f​l​y​
 ​ ​ ​ ​c​o​n​s​t​ ​c​h​a​r​ ​*​p​a​y​l​o​a​d​B​u​f​f​e​r​ ​=​ ​[​p​a​y​l​o​a​d​ ​U​T​F​8​S​t​r​i​n​g​]​;​

 ​ ​ ​ ​/​/​ ​N​o​t​e​:​ ​s​e​n​d​i​n​g​ ​l​e​n​g​t​h​ ​t​o​ ​a​n​ ​N​S​S​t​r​i​n​g​ ​w​i​l​l​ ​g​i​v​e​ ​u​s​ ​t​h​e​ ​n​u​m​b​e​r​
 ​ ​ ​ ​/​/​ ​o​f​ ​c​h​a​r​a​c​t​e​r​s​,​ ​n​o​t​ ​t​h​e​ ​n​u​m​b​e​r​ ​o​f​ ​b​y​t​e​s​,​ ​b​u​t​ ​s​t​r​l​e​n​
 ​ ​ ​ ​/​/​ ​g​i​v​e​s​ ​u​s​ ​t​h​e​ ​n​u​m​b​e​r​ ​o​f​ ​b​y​t​e​s​.​ ​(​S​o​m​e​ ​c​h​a​r​a​c​t​e​r​s​
 ​ ​ ​ ​/​/​ ​t​a​k​e​ ​u​p​ ​m​o​r​e​ ​t​h​a​n​ ​o​n​e​ ​b​y​t​e​ ​i​n​ ​U​n​i​c​o​d​e​)​
 ​ ​ ​ ​u​i​n​t​1​6​_​t​ ​p​a​y​l​o​a​d​L​e​n​g​t​h​ ​=​ ​h​t​o​n​s​(​s​t​r​l​e​n​(​p​a​y​l​o​a​d​B​u​f​f​e​r​)​)​;​

 ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​a​ ​b​i​n​a​r​y​ ​d​a​t​a​ ​c​o​n​t​a​i​n​e​r​ ​t​o​ ​p​a​c​k​ ​a​l​l​ ​o​f​ ​t​h​e​ ​d​a​t​a​
 ​ ​ ​ ​N​S​M​u​t​a​b​l​e​D​a​t​a​ ​*​d​a​t​a​ ​=​ ​[​N​S​M​u​t​a​b​l​e​D​a​t​a​ ​d​a​t​a​]​;​

 ​ ​ ​ ​/​/​ ​A​d​d​ ​e​a​c​h​ ​c​o​m​p​o​n​e​n​t​ ​i​n​ ​t​h​e​ ​r​i​g​h​t​ ​o​r​d​e​r​ ​t​o​ ​t​h​e​ ​d​a​t​a​ ​c​o​n​t​a​i​n​e​r​
 ​ ​ ​ ​[​d​a​t​a​ ​a​p​p​e​n​d​B​y​t​e​s​:​&​c​o​m​m​a​n​d​ ​l​e​n​g​t​h​:​s​i​z​e​o​f​(​u​i​n​t​8​_​t​)​]​;​

 ​ ​ ​ ​[​d​a​t​a​ ​a​p​p​e​n​d​B​y​t​e​s​:​&​i​d​e​n​t​i​f​i​e​r​ ​l​e​n​g​t​h​:​s​i​z​e​o​f​(​u​i​n​t​3​2​_​t​)​]​;​

 ​ ​ ​ ​[​d​a​t​a​ ​a​p​p​e​n​d​B​y​t​e​s​:​&​e​x​p​i​r​y​ ​l​e​n​g​t​h​:​s​i​z​e​o​f​(​u​i​n​t​3​2​_​t​)​]​;​

 ​ ​ ​ ​[​d​a​t​a​ ​a​p​p​e​n​d​B​y​t​e​s​:​&​t​o​k​e​n​L​e​n​g​t​h​ ​l​e​n​g​t​h​:​s​i​z​e​o​f​(​u​i​n​t​1​6​_​t​)​]​;​
 ​ ​ ​ ​[​d​a​t​a​ ​a​p​p​e​n​d​B​y​t​e​s​:​[​t​o​k​e​n​ ​b​y​t​e​s​]​ ​l​e​n​g​t​h​:​[​t​o​k​e​n​ ​l​e​n​g​t​h​]​]​;​

 ​ ​ ​ ​[​d​a​t​a​ ​a​p​p​e​n​d​B​y​t​e​s​:​&​p​a​y​l​o​a​d​L​e​n​g​t​h​ ​l​e​n​g​t​h​:​s​i​z​e​o​f​(​u​i​n​t​1​6​_​t​)​]​;​
 ​ ​ ​ ​[​d​a​t​a​ ​a​p​p​e​n​d​B​y​t​e​s​:​p​a​y​l​o​a​d​B​u​f​f​e​r​ ​l​e​n​g​t​h​:​s​t​r​l​e​n​(​p​a​y​l​o​a​d​B​u​f​f​e​r​)​]​;​

 ​ ​ ​ ​/​/​ ​I​n​c​r​e​m​e​n​t​ ​t​h​e​ ​i​d​e​n​t​i​f​i​e​r​ ​f​o​r​ ​t​h​e​ ​n​e​x​t​ ​n​o​t​i​f​i​c​a​t​i​o​n​
 ​ ​ ​ ​i​d​e​n​t​i​f​i​e​r​+​+​;​

 ​ ​ ​ ​r​e​t​u​r​n​ ​d​a​t​a​;​
}​

Now we need some way for the CocoaServer to specify the text of the message it sends to registered users. In the CocoaServer project, open MainMenu.xib. Add an NSTextField (not a label) and an NSButton to the interface. Then make outlets and actions and connect them as shown in Figure 29.9.

Figure 29.9  Finished CocoaServer XIB

Finished CocoaServer XIB

In CocoaServerAppDelegate.m, implement pushMessage: so that it sends the contents of the messageField to the device that is currently selected in the table view.

-​ ​(​I​B​A​c​t​i​o​n​)​p​u​s​h​M​e​s​s​a​g​e​:​(​i​d​)​s​e​n​d​e​r​
{​
 ​ ​ ​ ​/​/​ ​I​f​ ​y​o​u​ ​h​a​v​e​n​'​t​ ​s​e​l​e​c​t​e​d​ ​a​ ​r​o​w​,​ ​t​h​e​r​e​ ​i​s​ ​n​o​ ​o​n​e​ ​t​o​ ​s​e​n​d​
 ​ ​ ​ ​/​/​ ​t​h​e​ ​m​e​s​s​a​g​e​ ​t​o​
 ​ ​ ​ ​N​S​I​n​t​e​g​e​r​ ​r​o​w​ ​=​ ​[​t​a​b​l​e​V​i​e​w​ ​s​e​l​e​c​t​e​d​R​o​w​]​;​
 ​ ​ ​ ​i​f​ ​(​r​o​w​ ​=​=​ ​-​1​)​
 ​ ​ ​ ​ ​ ​ ​ ​r​e​t​u​r​n​;​

 ​ ​ ​ ​/​/​ ​P​u​l​l​ ​t​h​e​ ​m​e​s​s​a​g​e​ ​o​u​t​ ​o​f​ ​t​h​e​ ​t​e​x​t​ ​v​i​e​w​ ​a​n​d​ ​t​h​e​ ​t​o​k​e​n​
 ​ ​ ​ ​/​/​ ​o​f​ ​t​h​e​ ​d​e​v​i​c​e​ ​w​e​ ​a​r​e​ ​g​o​i​n​g​ ​t​o​ ​t​a​l​k​ ​t​o​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​m​s​g​T​e​x​t​ ​=​ ​[​m​e​s​s​a​g​e​F​i​e​l​d​ ​s​t​r​i​n​g​V​a​l​u​e​]​;​
 ​ ​ ​ ​N​S​D​a​t​a​ ​*​t​o​k​e​n​ ​=​ ​[​[​r​e​g​i​s​t​e​r​e​d​U​s​e​r​s​ ​o​b​j​e​c​t​A​t​I​n​d​e​x​:​r​o​w​]​ ​o​b​j​e​c​t​F​o​r​K​e​y​:​@​"​t​o​k​e​n​"​]​;​

 ​ ​ ​ ​N​S​D​a​t​a​ ​*​d​a​t​a​ ​=​ ​[​s​e​l​f​ ​n​o​t​i​f​i​c​a​t​i​o​n​D​a​t​a​F​o​r​M​e​s​s​a​g​e​:​m​s​g​T​e​x​t​ ​t​o​k​e​n​:​t​o​k​e​n​]​;​

 ​ ​ ​ ​/​/​ ​S​e​n​d​ ​t​h​i​s​ ​d​a​t​a​ ​o​u​t​ ​t​o​ ​A​p​p​l​e​'​s​ ​s​e​r​v​e​r​
 ​ ​ ​ ​[​w​r​i​t​e​S​t​r​e​a​m​ ​w​r​i​t​e​:​[​d​a​t​a​ ​b​y​t​e​s​]​ ​m​a​x​L​e​n​g​t​h​:​[​d​a​t​a​ ​l​e​n​g​t​h​]​]​;​
}​

Notice the use of write:maxLength: at the end of this method. To send data to a server with NSOutputStream, you send it this message. The write:maxLength: method takes a buffer of bytes and the length of that buffer as arguments. It returns the actual number of bytes written (or -1 if there was an error).

When you write to a stream, the data is essentially queued up for transfer. The stream will make the transfer when it can. In this case, the stream is scheduled into the main run loop. Therefore, when the main run loop is not processing another event, it will spend time flushing the output to the network interface. When the data has made its way out to the Internet, the write stream indicates to its delegate that it is ready to accept bytes again by sending the message stream:handleEvent: with NSStreamEventHasSpaceAvailable as the event type.

Now it’s time to see what you have wrought. Build and run CocoaServer. Then, build and run Notified. After the device appears in CocoaServer’s table view, exit Notified by pressing the Home button. In CocoaServer, select the device in the table view, enter a message into the text field, and press Push Message. Wait a moment for the notification server to do its part, and then check your device for the notification. How awesome is that?

Detecting errors in notification delivery

If there is an error delivering an enhanced notification, the server will respond with an error. This data will be 6 bytes long. The first byte is a command and will always be the value 8. The second byte is a status code that indicates the type of the error. (You can see all of the status codes in the documentation.) The final 4 bytes is the identifier of that notification.

When a read stream has data available, it sends the message stream:handleEvent: to its delegate. Then, you send the message read:maxLength: to the stream that has the data, passing an allocated buffer to hold the result of the read along with the size of that buffer. The NSInputStream will take the bytes off the stream and put them into the buffer until it reaches the number of bytes you specified. The value returned by this method is a number. A positive number indicates the number of bytes that were actually transferred to the buffer. The value is 0 if there are no bytes left to read and -1 if there was an error.

Typically, when you read from an input stream, you pick a reasonable size (based on the size of the data you are planning to receive) for a buffer, read that many bytes, and then repeat until the buffer returns 0.

We know that a notification delivery error is always 6 bytes. Thus, we can read 6 bytes at a time from the stream. In CocoaServerAppDelegate.m, locate the method stream:handleEvent: and add the following code:

 ​ ​ ​ ​ ​ ​ ​ ​c​a​s​e​ ​N​S​S​t​r​e​a​m​E​v​e​n​t​H​a​s​B​y​t​e​s​A​v​a​i​l​a​b​l​e​:​
 ​ ​ ​ ​ ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​i​f​ ​(​a​S​t​r​e​a​m​ ​=​=​ ​r​e​a​d​S​t​r​e​a​m​)​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​I​f​ ​d​a​t​a​ ​c​a​m​e​ ​b​a​c​k​ ​f​r​o​m​ ​t​h​e​ ​s​e​r​v​e​r​,​ ​w​e​ ​h​a​v​e​ ​a​n​ ​e​r​r​o​r​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​L​e​t​'​s​ ​f​e​t​c​h​ ​i​t​ ​o​u​t​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​N​S​U​I​n​t​e​g​e​r​ ​l​e​n​g​t​h​R​e​a​d​ ​=​ ​0​;​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​d​o​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​E​r​r​o​r​ ​p​a​c​k​e​t​ ​i​s​ ​a​l​w​a​y​s​ ​6​ ​b​y​t​e​s​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​u​i​n​t​8​_​t​ ​*​b​u​f​f​e​r​ ​=​ ​m​a​l​l​o​c​(​6​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​l​e​n​g​t​h​R​e​a​d​ ​=​ ​[​r​e​a​d​S​t​r​e​a​m​ ​r​e​a​d​:​b​u​f​f​e​r​ ​m​a​x​L​e​n​g​t​h​:​6​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​F​i​r​s​t​ ​b​y​t​e​ ​i​s​ ​c​o​m​m​a​n​d​ ​(​a​l​w​a​y​s​ ​8​)​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​u​i​n​t​8​_​t​ ​c​o​m​m​a​n​d​ ​=​ ​b​u​f​f​e​r​[​0​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​S​e​c​o​n​d​ ​b​y​t​e​ ​i​s​ ​t​h​e​ ​s​t​a​t​u​s​ ​c​o​d​e​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​u​i​n​t​8​_​t​ ​s​t​a​t​u​s​ ​=​ ​b​u​f​f​e​r​[​1​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​T​h​i​s​ ​w​i​l​l​ ​b​e​ ​t​h​e​ ​n​o​t​i​f​i​c​a​t​i​o​n​ ​i​d​e​n​t​i​f​i​e​r​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​u​i​n​t​3​2​_​t​ ​*​i​d​e​n​t​ ​=​ ​(​u​i​n​t​3​2​_​t​ ​*​)​(​b​u​f​f​e​r​ ​+​ ​2​)​;​

 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​E​R​R​O​R​ ​W​I​T​H​ ​N​O​T​I​F​I​C​A​T​I​O​N​:​ ​%​d​ ​%​d​ ​%​d​"​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​(​i​n​t​)​c​o​m​m​a​n​d​,​ ​(​i​n​t​)​s​t​a​t​u​s​,​ ​*​i​d​e​n​t​)​;​

 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​r​e​e​(​b​u​f​f​e​r​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​}​ ​w​h​i​l​e​(​l​e​n​g​t​h​R​e​a​d​ ​>​ ​0​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​}​
 ​ ​ ​ ​ ​ ​ ​ ​}​ ​b​r​e​a​k​;​

Now, if there is an issue with your notification data, the console will show you why.

More on reading from a stream

This stream-reading technique is sufficient for our application because we know the exact size of the data we will get back from the server. In general, however, you have to take more precautions. Sometimes, you will get more than one packet, a partial packet, or a combination of the two (e.g., one and a half packets). Sometimes, data of variable sizes will be sent. The solution to these problems is creating a packet format that both the client and server agree upon. Take a look at the notification data you prepare for the notification server: it has a format that is set-in-stone and you always pass the size of the variable parts of that data. By being a notification provider, you have agreed to this format.

A typical format uses the first byte as a command that indicates the format of the data. Since you always receive at least one byte when the stream informs its delegate that it has bytes available, you can safely read this byte and determine what to do next. Many formats use the next chunk of bytes to indicate the length of the rest of the packet. So, the stream will check the command and say, Oh, this is a Message command, so I know that the next 2 bytes will be the length of the packet. I’ll read those two bytes to figure out how large the packet is.

The delegate will then attempt to read that many bytes from the stream. If there are exactly that many bytes left in the read stream, you have a single and complete packet from the server and can use that data in your application.

In practice, though, that doesn’t always happen. If the data you read is not the exact length you are expecting, you will have to store that data for later. Let’s consider two examples:

  • You read the first three bytes of a stream, and they indicate that there are 40 more bytes in the packet. You allocate a buffer that is 40 bytes and attempt to read 40 bytes from the stream – but the stream only has 32 bytes available. In this situation, you keep that data around. Then, the next time a stream informs its delegate that bytes are available, you immediately lop off the first 8 bytes and append them to the stored data. Now, you have a complete packet.
  • You read the first three bytes and again know that there are 40 bytes left in the packet. But, there are actually 60 bytes available. The first 40 bytes are a complete packet – you can use that data immediately. However, the last 20 bytes belong to another packet. So, you grab the first byte to determine the format. Let’s assume the same format, so you check the next two bytes for the length and see that the packet is 36 bytes. You keep the extra bytes around, and the next chunk of data that comes in will be appended to it.

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

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