As a courtesy, this is a full free rendering of my book, Programming iOS 6, by Matt Neuburg. Copyright 2013 Matt Neuburg. Please note that this edition is outdated; the current books are iOS 10 Programming Fundamentals with Swift and Programming iOS 10. If my work has been of help to you, please consider purchasing one or both of them. Thank you!

Chapter 37. Basic Networking

Networking is difficult and complicated, chiefly because it’s ultimately out of your control. My motto with regard to the network is, “There’s many a slip ’twixt the cup and the lip.” You can ask for a resource from across the network, but at that point anything can happen: the resource might not be found (the server is down, perhaps), it might take a while to arrive, it might never arrive, the network itself might vanish after the resource has partially arrived. iOS, however, makes at least the basics of networking very easy, so that’s what this chapter will deal with.

Many earlier chapters have described interface and frameworks that network for you automatically. Put a UIWebView in your interface (Chapter 24) and poof, you’re networking; the UIWebView does all the grunt work, and it does it a lot better than you’d be likely to do it from scratch. The same is true of MPMovieViewController (Chapter 28), MFMailComposeViewController (Chapter 33), and MKMapView (Chapter 37).

HTTP Requests

A simple HTTP request is made through an NSURLConnection object. You hand it an NSURLRequest describing what you’d like to do, and start the download. The actual network operations happen asynchronously (unless you specifically demand that they happen synchronously, which you’d never do); in other words, the NSURLConnection object does all its work in the background. Data received from the network in response to your request will arrive as an NSData object.

For the very simplest cases, you can download a resource asynchronously without using a delegate: call the class method sendAsynchronousRequest:queue:completionHandler:. This creates an NSURLConnection and starts the download immediately. When the download ends, whether in failure or success, the completion handler block is called on the NSOperationQueue you specified, with three parameters: an NSURLResponse, an NSData (which will be the entire download if the download succeeded), and an NSError object. Here’s an example of downloading a JPEG image file and displaying it in the interface; I specify the main queue (the queue of the main thread), because my completion handler is going to talk directly to my app’s interface (see also Chapter 38):

NSString* s = @"http://www.someserver.com/somefolder/someimage.jpg";
NSURL* url = [NSURL URLWithString:s];
NSURLRequest* req = [NSURLRequest requestWithURL:url];
NSOperationQueue* q = [NSOperationQueue mainQueue];
[NSURLConnection sendAsynchronousRequest:req queue:q
   completionHandler:^(NSURLResponse *resp, NSData *d, NSError *err) {
       if (d) {
           UIImage* im = [UIImage imageWithData:d];
           self.iv.image = im;
       }
   }];

The more formal and comprehensive approach is to specify the NSURLRequest along with a delegate. When the download starts, you stand back and let delegate messages arrive. To obtain and initialize an NSURLConnection object using this approach, call one of the following:

connectionWithRequest:delegate:
initWithRequest:delegate:
The download begins immediately.
initWithRequest:delegate:startImmediately:
This is the designated initializer; the other two methods call it. If the last argument is NO, the download does not begin until you send the connection the start message. You can specify an NSOperationQueue (Chapter 38) with setDelegateQueue: if you’d like the delegate messages to arrive on a background thread.

The data will arrive piecemeal, so you have to maintain state; in particular, you’ll prepare an NSMutableData object (probably as an instance variable, as it needs to persist while different methods refer to it) to which you’ll keep appending each new chunk of NSData until you’re told that the entire data has arrived — or that the request has failed. (The whole process is somewhat reminiscent of what we did with an NSXMLParser in Chapter 36.)

All the real work happens in four delegate methods:

connection:didReceiveResponse:
The server is responding. We can now hope that our data will start to arrive, so get ready. If you like, you can interrogate the NSURLResponse object that is handed to you, to learn things from the response headers such as the data’s expected size and MIME type. You can also ask for the originalRequest and the currentRequest to learn whether redirects or other forces have altered the NSURLRequest that is now being fulfilled.
connection:didReceiveData:
Some data has arrived. Append it to the NSMutableData object.
connectiondidFinishLoading:
All of the data has arrived; the NSMutableData object presumably contains it. Clean up as needed.
connection:didFailWithError:
Something went wrong. Clean up as needed.

Here’s an example of initiating a download of a JPEG image file:

self.receivedData = [NSMutableData data];
NSString* s = @"http://www.someserver.com/somefolder/someimage.jpg";
NSURL* url = [NSURL URLWithString:s];
NSURLRequest* req = [NSURLRequest requestWithURL:url];
NSURLConnection* conn =
    [NSURLConnection connectionWithRequest:req delegate:self];

Here are the corresponding delegate method implementations:

- (void) connection:(NSURLConnection *)connection
        didReceiveResponse:(NSURLResponse *)response {
    // connection is starting, clear buffer
    [self.receivedData setLength:0];
}

- (void) connection:(NSURLConnection *)connection
        didReceiveData:(NSData *)data {
    // data is arriving, add it to the buffer
    [self.receivedData appendData:data];
}

- (void)connection:(NSURLConnection*)connection
        didFailWithError:(NSError *)error {
    // something went wrong, clean up interface as needed
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    // all done, we are ready to rock and roll
    // do something with self.receivedData
}

You should be wondering at this point how memory management works for an NSURLConnection. We don’t retain the NSURLConnection, so how does it live long enough to do any downloading? The answer is that NSURLConnection memory management works like NSTimer memory management (Chapter 12): as the download starts, the run loop retains it (and doesn’t release it until the connection fails, finishes, or is canceled). Both connectionWithRequest:delegate: and initWithRequest:delegate: begin the download immediately, so the connection object that they return is retained by the run loop and doesn’t need to be retained elsewhere.

On the other hand, an NSURLConnection initialized with initWithRequest:delegate:startImmediately:, as I mentioned earlier, does not start immediately if the third argument is NO, and you’ll want to keep a reference to it in order to send it the start message later; so, in the general case, we ought to have an NSURLConnection property with a retain policy. If we’re going to do that, we should probably wrap the entire connection process in a dedicated object to hold this instance variable, because otherwise keeping track of multiple simultaneous NSURLConnections would be a nightmare. Here’s the complete implementation for such a wrapper object, MyDownloader:

// MyDownloader.h:

@interface MyDownloader : NSObject
@property (nonatomic, strong, readonly) NSURLConnection* connection;
@property (nonatomic, strong, readonly) NSData* receivedData;
- (id) initWithRequest: (NSURLRequest*) req;
- (void) cancel;
@end

// MyDownloader.m:

@interface MyDownloader ()
@property (nonatomic, strong, readwrite) NSURLConnection* connection;
@property (nonatomic, copy, readwrite) NSURLRequest* request;
@property (nonatomic, strong, readwrite) NSMutableData* mutableReceivedData;
@end

@implementation MyDownloader

- (NSData*) receivedData {
    return [self.mutableReceivedData copy];
}

- (id) initWithRequest: (NSURLRequest*) req {
    self = [super init];
    if (self) {
        self->_request = [req copy];
        self->_connection =
            [[NSURLConnection alloc] initWithRequest:req
                delegate:self startImmediately:NO];
        self->_mutableReceivedData = [NSMutableData new];
    }
    return self;
}

- (void) connection:(NSURLConnection *)connection
        didReceiveResponse:(NSURLResponse *)response {
    [self.mutableReceivedData setLength:0];
}

- (void) connection:(NSURLConnection *)connection
        didReceiveData:(NSData *)data {
    [self.mutableReceivedData appendData:data];
}

- (void)connection:(NSURLConnection *)connection
        didFailWithError:(NSError *)error {
    [[NSNotificationCenter defaultCenter]
        postNotificationName:@"connectionFinished"
        object:self userInfo:@{@"error": error}];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [[NSNotificationCenter defaultCenter]
        postNotificationName:@"connectionFinished" object:self];
}

- (void) cancel {
    // cancel download in progress, replace connection, start over
    [self.connection cancel];
    self->_connection =
        [[NSURLConnection alloc] initWithRequest:self->_request
            delegate:self startImmediately:NO];
}
@end

The class uses a combination of private and redeclared properties along with an explicit getter to make certain that clients have read-only access to instance variables (and, in the case of our NSMutableData object, access only to an immutable copy). Communication back to the client when the download finishes is through a notification; it is up to the client to register for this notification beforehand.

In the line that creates the NSURLConnection, we have used the designated initializer with a startImmediately: argument value of NO. Thus, a MyDownloader object can exist and be ready for action before doing any actual downloading. To set the download into motion, we tell the MyDownloader’s connection to start. (Sending start to an NSURLConnection that is already downloading has no effect.) In the past, there have been complaints that sending start to an NSURLConnection that does not start immediately can cause a crash. I have not seen this myself, so perhaps it has been fixed in more recent iOS versions, but the solution is to schedule the connection on a run loop explicitly just before starting it:

[connection scheduleInRunLoop:[NSRunLoop currentRunLoop]
                      forMode:NSDefaultRunLoopMode];
[connection start];

The following sentence in the NSURLConnection header file may cause some concern: “The delegate is retained by the NSURLConnection until a terminal condition is encountered.” MyDownloader retains the NSURLConnection and is its delegate, which raises a worry that a retain cycle may be looming in our future. However, in practice this should cause no difficulty; as that sentence implies, the delegate is released when the NSURLConnection is no longer downloading, so one way or another a leak will be avoided with no special action on our part. The delegate is retained for the same reason that an NSTimer’s target is retained: if the delegate were to go out of existence while the download is ongoing, the attempt to send it delegate messages could cause a nasty crash. In any case we cannot set an NSURLConnection’s delegate to nil, as it has no delegate property. If a download needs to be abandoned in midflight, the client should send us the cancel message, and the download will be stopped in the background in good order.

An NSURLConnection that has started downloading can be canceled by sending it the cancel message, and MyDownloader’s implementation of cancel does this. However, an NSURLConnection that has been canceled is then good for nothing; it cannot be reused to try to start a connection ever again. Therefore, MyDownloader’s cancel implementation also replaces its NSURLConnection with a fresh NSURLConnection configured in the same way, in case the client wants to try again later. The same thing is true of an NSURLConnection that has failed or that has finished in good order: it, too, is then good for nothing. However, MyDownloader does not replace its NSURLConnection in that case, because the client is expected to abandon the MyDownload instance completely at that point.

How would we use MyDownloader if we have several objects to download? We might, for example, keep a mutable array of MyDownloader objects. To initiate a download, we create a MyDownloader object, register for its @"connectionFinished" notification, stuff it into the array, and set its connection going:

if (!self.connections)
    self.connections = [NSMutableArray array];
NSString* s = @"http://www.someserver.com/somefolder/someimage.jpg";
NSURL* url = [NSURL URLWithString:s];
NSURLRequest* req = [NSURLRequest requestWithURL:url];
MyDownloader* d = [[MyDownloader alloc] initWithRequest:req];
[self.connections addObject:d];
[[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(finished:) name:@"connectionFinished" object:d];
[d.connection start];

When the notification arrives, either we’ve failed with an error or we’ve finished in good order. In the latter case, we grab the received data; either way, we remove the MyDownloader from the array, thus releasing it, along with its connection and its data:

- (void) finished: (NSNotification*) n {
    MyDownloader* d = [n object];
    NSData* data = nil;
    if ([n userInfo]) {
        // ... error of some kind! ...
    } else {
        data = d.receivedData;
        // ... and do something with the data right now ...
    }
    [[NSNotificationCenter defaultCenter]
        removeObserver:self name:@"connectionFinished" object:d];
    [self.connections removeObject:d];
}

In real life, you’d probably subclass MyDownloader to fit some particular task, and incorporate your downloaders directly into your application’s model, letting them fetch the data on demand. Suppose, for example, you need to download images to serve as thumbnails in the cells of a UITableView. Let’s consider how these images can be supplied lazily on demand. The model, as we saw in Chapter 21, might be an array of dictionaries. In this case, the dictionary might contain some text and a downloader whose job is to supply the image. So what I’m proposing is a model like this:

array
    dictionary
        text: @"Manny"
        pic: Downloader whose job is to supply an image of Manny
    dictionary
        text: @"Moe"
        pic: Downloader whose job is to supply an image of Moe
    dictionary
        text: @"Jack"
        pic: Downloader whose job is to supply an image of Jack
    ....

When the table turns to the data source for data, the data source will turn to the dictionary corresponding to the requested row, and ask that dictionary’s downloader for its image. At that point, either the downloader has an image, in which case it supplies it, or it hasn’t, in which case it returns nil (or some placeholder) and begins the download.

Here’s the key point. When a downloader succeeds in downloading its image, it notifies the data source. If the corresponding row is visible, the data source immediately tells the table to reload the corresponding row; the table once again asks the data source for the data, the data source once again turns to the dictionary corresponding to the requested row and once again asks that dictionary’s downloader for its image, and this time it obtains the image! Moreover, once an image is downloaded, the downloader continues to hold on to it and to supply it on request, so as the user scrolls, previously downloaded images just appear as part of the table.

The downloader we’re imagining here is a MyDownloader subclass, MyImageDownloader, with an image property so that the data source can request the image. MyImageDownloader’s implementation is straightforward. The data source is not going to abandon a MyImageDownloader that has failed — it isn’t going to abandon any MyImageDownloader, because a MyImageDownloader either knows how to fetch the image or is the vendor of the fetched image — so we override connection:didFailWithError: to replace the now useless NSURLConnection, allowing the download to be attempted again later. We also override cancel to do nothing if the download is complete:

- (UIImage*) image {
    if (self->_image)
        return self->_image;
    [self.connection start];
    return nil; // or a placeholder
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    UIImage* im = [UIImage imageWithData:self.receivedData];
    if (im) {
        self.image = im;
        [[NSNotificationCenter defaultCenter]
            postNotificationName:@"imageDownloaded" object:self];
    }
}

- (void)connection:(NSURLConnection *)connection
        didFailWithError:(NSError *)error {
    // prepare to try again
    self.connection =
        [[NSURLConnection alloc] initWithRequest:self.request
            delegate:self startImmediately:NO];
}

- (void) cancel {
    if (!self.image) // no point canceling if we finished the download
        [super cancel];
}

The data source looks perfectly normal:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    NSDictionary* d = (self.model)[indexPath.row];
    cell.textLabel.text = d[@"text"];
    MyImageDownloader* imd = d[@"pic"];
    cell.imageView.image = imd.image;
    return cell;
}

Now for the key point. The data source is also registered for an @"imageDownloaded" notification. When such a notification arrives, it works out the table row corresponding to the MyImageDownloader that posted the notification and reloads that row:

- (void) imageDownloaded: (NSNotification*) n {
    MyImageDownloader* d = [n object];
    NSUInteger row = [self.model indexOfObjectPassingTest:
                      ^BOOL(id obj, NSUInteger idx, BOOL *stop) {
                          return (((NSDictionary*)obj)[@"pic"] == d);
                      }];
    if (row == NSNotFound) return; // shouldn't happen
    NSIndexPath* ip = [NSIndexPath indexPathForRow:row inSection:0];
    NSArray* ips = [self.tableView indexPathsForVisibleRows];
    if ([ips indexOfObject:ip] != NSNotFound) {
        [self.tableView reloadRowsAtIndexPaths:@[ip]
                              withRowAnimation:UITableViewRowAnimationFade];
    }
}

A new feature in iOS 6 is that the table view delegate is notified when a cell scrolls out of view. We can take advantage of this to increase our efficiency and reduce our demands for bandwidth; if that cell’s MyImageDownloader was still downloading its image, we can stop it, thus limiting the maximum number of images that will ever be requested simultaneously to the number of visible rows:

-(void)tableView:(UITableView *)tableView
        didEndDisplayingCell:(UITableViewCell *)cell
        forRowAtIndexPath:(NSIndexPath *)indexPath {
    NSDictionary* d = (self.model)[indexPath.row];
    MyImageDownloader* imd = d[@"pic"];
    [imd cancel];
}

The only missing piece of the puzzle is what should happen when a cell’s downloader fails. If the user scrolls the failed cell out of view and later scrolls it back into view, the table will ask the data source for its data and the MyImageDownloader will try again to download its image. But that won’t happen for a failed cell that’s never scrolled out of view. How you deal with this is up to you; it’s a matter of providing the best user experience without having an undue impact upon performance, battery, and so forth. In this instance, because these images are fairly unimportant, I might arrange that when an NSTimer with a fairly large interval fires (every 60 seconds, say), we reload the visible rows; this will cause any failed MyImageDownloader whose corresponding row is visible to try again.

In planning your interface, it is useful to draw a distinction as to whether the user will experience a particular networking session explicitly or implicitly. This changes nothing about how you network; it’s a matter of presentation. Downloading images to be slotted into the cells of an existing table view would presumably be implicit networking: it happens regardless of whether the user wants it, and it doesn’t seriously affect overall functionality, even if some or all of the images fail to arrive. In the TidBITS News app, on the other hand, everything displayed comes from a downloaded RSS feed: no feed, no data. This is explicit networking; the user needs to know when we are using the network, and needs to be informed of failure. The app preserves the previously downloaded feed, so the user has something to read even in the absence of the network, but the feed is explicitly refreshed when the user summons the table’s UIRefreshControl, along with the spinning network activity indicator (Chapter 25); if the download fails, we put up an alert.

Bonjour

Bonjour is the ingenious technology, originated at Apple and now becoming a universal standard, for allowing network devices to advertise services they provide and to discover dynamically other devices offering such services. Once an appropriate service is detected, a client device can resolve it to get a network address and can then begin communicating with the server device. Actually communicating is outside the scope of this book, but device discovery via Bonjour is easy.

In this example, we’ll look to see whether any device, such as a Mac, is running iTunes with library sharing turned on. We can search for domains or for a particular service; here, we’ll pass the empty string as the domain to signify “any domain,” and concentrate on the service, which is @"_daap._tcp". We maintain two instance variables, the NSNetServiceBrowser that will look for devices, and a mutable array in which to store any services it discovers:

self.services = [NSMutableArray array];
NSNetServiceBrowser* browser = [NSNetServiceBrowser new];
self.nsb = browser;
self.nsb.delegate = self;
[self.nsb searchForServicesOfType:@"_daap._tcp" inDomain:@""];

The NSNetServiceBrowser is now searching for devices advertising iTunes sharing and will keep doing so until we destroy it or tell it to stop. It is common to leave the service browser running, because devices can come and go very readily. As they do, the service browser’s delegate (NSNetServiceBrowserDelegate) will be informed. For purposes of this example, I’ll simply maintain a list of services, and update the app’s interface when the situation changes:

- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
           didFindService:(NSNetService *)netService
               moreComing:(BOOL)moreServicesComing {
    [self.services addObject:netService];
    if (!moreServicesComing)
        [self updateInterface];
}

- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
         didRemoveService:(NSNetService *)netService
               moreComing:(BOOL)moreServicesComing {
    [self.services removeObject:netService];
    if (!moreServicesComing)
        [self updateInterface];
}

The delegate messages very kindly tell me whether they have finished listing a series of changes, so I can wait to update the interface until after a full batch of changes has ended. In this example, I don’t really have any interface to update; I’ll just log the list of services, each of which is an NSNetService instance:

- (void) updateInterface {
    for (NSNetService* service in self.services) {
        if (service.port == -1) {
            NSLog(@"service %@ of type %@, not yet resolved",
                service.name, service.type);
        }
    }
}

To connect to a service, we would first need to resolve it, thus obtaining an address and other useful information. An unresolved service has port -1, as shown in the previous code. To resolve a service, you tell it to resolve (resolveWithTimeout:); you will probably also set a delegate on the service (NSNetServiceDelegate), so as to be notified when the resolution succeeds (or fails). Here, I’ll have the delegate call my updateInterface method again if a resolution succeeds, and I’ll extend updateInterface to show the port number for any resolved services:

- (void) updateInterface {
    for (NSNetService* service in self.services) {
        if (service.port == -1) {
            NSLog(@"service %@ of type %@, not yet resolved",
                service.name, service.type);
            [service setDelegate:self];
            [service resolveWithTimeout:10];
        } else {
            NSLog(@"service %@ of type %@, port %i, addresses %@",
                service.name, service.type, service.port, service.addresses);
        }
    }
}

- (void)netServiceDidResolveAddress:(NSNetService *)sender {
    [self updateInterface];
}

The addresses of a resolved service constitute an array of NSData. Logging an address like this is largely pointless, as it is not human-readable, but it’s useful for handing to a CFSocket. In general you’ll call the service’s getInputStream:outputStream: to start talking over the connection; that’s outside the scope of this discussion. See Apple’s WiTap example for more.

Push Notifications

If your app uses a server on the network that’s under your control, you can arrange for the user to be notified when a significant event takes place on the server. This is called a push notification (or remote notification). The user interface for a push notification is the same as for a local notification, and the user can disable your app’s notifications altogether (Chapter 26).

For example, the TidBITS News app is about news stories on the TidBITS website. The app’s data comes from an RSS feed, which is refreshed on the server side whenever something changes on the site, such as a new news story being posted. It might be appropriate (and cool) if we were to add push notifications to the server code that refreshes the RSS feed, so that users could be alerted to the fact that they might like to launch TidBITS News and read a newly posted story.

Implementing push notifications is not trivial, and requires cooperation across the network between your app and your server, and between your server and Apple’s push notification server. I’ve never actually tried this, so I’m just describing what the architecture is like; for details, read Apple’s Local and Push Notification Programming Guide.

When developing your app, you obtain from the iOS Provisioning Portal (Chapter 9) credentials identifying your app, and allowing communication between your server and Apple’s push notification server, and between Apple’s push notification server and your app running on the user’s device. When your app launches, it calls the UIApplication method registerForRemoteNotificationTypes:, which communicates asynchronously with Apple’s push notification server to obtain a token identifying this instance of your app. If successful, the token comes back in the app delegate method application:didRegisterForRemoteNotificationsWithDeviceToken:. At that point, your app must communicate with your server to provide it with this token.

The server is now maintaining two pieces of information: its credentials and a list of tokens effectively representing users. When an event occurs at your server for which the server wishes to push a notification out to users, the server uses its credentials to connect with Apple’s push notification server and — for every individual user whom the server wishes to notify — streams a message to Apple’s push notification server, providing the user token plus a “payload” that describes the notification, much as a UILocalNotification does (Chapter 26). The payload is written in JSON (Chapter 36).

Meanwhile, the user’s device, if it is still on, is (with luck) connected to the network in a low-power mode that allows it to hear from Apple’s push notification server. The push notification server sends the message to the user’s device, where the system treats it much like a local notification. If the user summons your app through the notification interface, your app can learn what has happened through either the app delegate message application:didReceiveRemoteNotification: or (if the app had to be launched from scratch) through application:didFinishLaunchingWithOptions:, whose dictionary will contain UIApplicationLaunchOptionsRemoteNotificationKey. The notification itself, instead of being a UILocalNotification object, is an NSDictionary corresponding to the original JSON payload.

Beyond Basic Networking

There are many aspects of basic networking that I haven’t gone into in this chapter. For example:

  • An NSURLRequest has a cache policy, which you can set to determine whether the request might be satisfied without freshly downloading previously downloaded data.
  • An NSURLRequest to be handed to an NSURLConnection can specify that it wants to use the FTP, HTTP, or HTTPS scheme, including POST requests.
  • An NSURLConnection can handle redirects and authentication.

See the URL Loading System Programming Guide.

You can also get as deep into the details of networking as you like; see in particular the CFNetwork Programming Guide.

Apple provides a generous amount of sample code. See in particular SimpleURLConnections, AdvancedURLConnections, SimpleNetworkStreams, SimpleFTPSample, and MVCNetworking.