Core OS • iOS, OS X • 55:14
The NSURLSession class provides powerful HTTP networking features in iOS and OS X. Learn about new NSURLSession capabilities and hear expert advice on practical, efficient and high performance networking for your apps.
Speakers: Steve Algernon, Scott Marshall, Dan Vinegrad
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Welcome to this afternoon's, this year's edition of "What's New in Foundation Networking." Foundation Networking is, of course, the layer of networking that's available to application developers on Mac OS X and iOS. It's our goal to provide you guys with the protocol and networking support that you need to make great applications on our platform. And, of course, we use these APIs ourselves.
Here's a standard picture of what frameworks look like on our system. Your application sits on top, and nowadays you're also having extensions that are sitting on top of all these frameworks. Extensions are particularly interesting for background networking because when your extension terminates, you might still need to have work done, and you can use background networking through NSURLSession to get that work done.
Looking at the different levels that actually make up Foundation Networking, that we start with the Core OS BSD networking layer. This is the lowest level that sockets its bind, its addresses. It's kind of like building a car from a kit. Back when Mac OS started, we took the foundation layer of things from NeXTSTEP, and we said, "Well, we need something that's a C API." And CoreFoundation came out of that. And CFNetwork came out of that. As time has gone on, we find more and more developers want a higher level approach. Foundation and Foundation Networking provides NSURLSession, replacing NSURLConnection, NSStream, and NSNetServices as Cocoa-style APIs for developers to use on Mac OS X and iOS.
These APIs serve different purposes. NSStream is an API that is used to synch bytes into the system in various places or to receive bytes out when you maybe don't have an idea of how many bytes there are. You could have a file on disk and you know that it's a certain size. So you create an NSInputStream from a file.
Or you could receive an NSInputStream from an API. You don't know how big it is, so you schedule the stream and then you start reading bytes out of it. NSNetServices is an API that allows you to do peer-to-peer servers and clients on your machine, on your device, talking to other devices. It's a great way of publishing work that you want other apps to do.
NSURLSession provides sort of a message-based delivery. You're talking about putting a request on the network and receiving a response and some data code. In this talk, we're going to talk about those APIs: NSStream NSNetServices and NSURLSession. We're going to review NSURLSession, which is what we introduced last year.
So there will be a bit of review here. We have some new protocol support, and if you saw Craig's talk yesterday, there's on one slide there was a little mention of a new protocol that we'll talk about. And lastly we're going to spend some time talking about the best way to use NSURLSession and background networking.
Let's get the new APIs out of the way first. NSNetServices has a single, new API. But it's not really new because it was available last year in iOS 7. This year includes peer-to-peer is available on Mac OS X Yosemite on hardware that supports it which is recent, 2012 or later Mac hardware. That's really the only API change available, but it is now available for you to use. NSStream gets two new APIs. Now NSStream is-it's Toll-Free Bridged with CFStream, which means that there's a lower level API that this works with.
But at some point we lost the ability of just creating a stream to a host on the internet. So adding back a new API gets streams to host with name specifying a port. Then the host could be a dotted IP, IPv6 or regular internet host name. The result is an inputStream and an outputStream that you then open, schedule, and read and write from as those streams get opened or if you pass on to another layer. So, for instance, if you had an audio streaming app that wanted to read bytes off of a network resource that was providing you with audio data, you could use this API to get an NSInputStream from that and pass that NSInputStream off to the audio player.
Well, if you had bytes that you were generating that you wanted to send into that audio player, or if you wanted to filter those bytes, you might need to implement your own NSStream. You could do that by subclassing NSStream or the preferred way we'd want you to do it is creating what we call a "bound pair". That creates an input and output stream and a buffer. So you'll write bytes into the output stream, and the audio unit or whatever it is will consume the bytes out of the input stream. So that's just a couple of new APIs in NSNetServices and NSStream.
I'm going to talk about NSURLSession. And a lot of this is review from last year. NSURLSession is sort of a big machine that has a lot of moving parts. And in this picture it kind of looks like a boat. It's an object that you create with a configuration and it references storage objects. The storage objects are needed to supply information about how to perform a load.
Well, if it's a boat, the captain of the boat is your delegate that you bind into the system. The purpose of this boat as it's running along is to take data in a request and to produce responses and data objects out. So it's like carrying passengers and a duty-free shop out the other side where you get whatever it is that you're getting. All right.
So the concepts of an NSURLSession. The session object itself. The API is called an NSURLSession. The main factory class is called an NSURLSession. An NSURLSessionClass is created with an NSURLSession configuration object. The configuration object is a dictionary of properties that dictate things like the how many connections we can make to a server. What kind of SSL we're going to use. What storage objects we're going to use. The session creates task objects. These represent the sort of transient state of a resource as it's being loaded. A task object is created from a request or a URL.
And its initial state is suspended. You resume it; it does its work within the session, conferring with your delegate as needed, and in the end the task becomes finished. And you're done; you've got your data. Okay. The delegate itself is something you supply. The delegate is has to conform to a bunch of different protocols. There are protocols for session, for task and for the various task subclasses.
There are storage objects that exist in the system by default. There is a Keychain-based credential store. There is a cookie store-a cookie jar. Cookies are sort of a degenerate form of authentication. They serve the same purpose. They identify you to a website, and allow that website to communicate with you in what it thinks is a secure way.
Protocols are objects that you can register with a session that override the default behavior or provide additional behavior. So a URL looks like "HTTP:", your protocol could be "MOOF:" and you could deal with MOOF protocols however you want, including rewriting them to become HTTP protocols. Lastly, there's the URL cache object, which is what we're able to consult in order to avoid going out on the network at all. In NSURLSession, the other configuration object, you can specify your own subclasses for all of these storage objects.
The configuration object itself, as I mentioned, it basically is a dictionary although it is comprised of a bunch of property attributes. When you create one, when you get a configuration object, you modify it however you want. And then you're going to create a session with that configuration. Once you've created a session with a configuration, you can't modify it again.
These are some of the attributes that you can change within a configuration: the TLS levels, whether you want to support the latest TLS level, restrict it to that, or to allow the previous one, whether or not to allow a request to go over the network (cell network). Network service type is, well, "I'm doing video data. I want a higher priority if that's available." I won't try and list all the configuration options, because there are quite a few and they're all in the header file.
To get a configuration object, you can ask for the default session configuration. This is a factory method that returns an object that captures the global state of the framework. If you look at the Foundation Framework, there are some classes like NSData that have a class method, NSData dataWithContentsOfURL. If you were to pass an HTTP URL into that, it would use all the global defaults of the Foundation Framework in order to go out on the network and get that data.
Never call that API because it blocks and it will end up hanging whatever thread you're executing it on. The point is that that configuration is what the default session configuration represents. If you get a default session configuration and modify it, you're only modifying that configuration. You're not modifying the global configuration.
There's a factory method for an ephemeral session configuration. This is set up so that none of the storage objects actually persist data to this. You would use it in private browsing, for instance. The third factory method is used to create a background session. You specify an identifier which is a string that, when your app is re-launched, you use the same string in order to get re-associated with that session in the background. Then you'll start receiving events for that session.
There's a factory method for a session that gives you a shared session. The shared session is, well, going back to that NSData dataWithContentsOfURL example, that would be using the shared session to do its work. It's representing the global environment. It's a very handy thing to use when you just want to get a resource off the network.
Once you have a configuration object that's set up though, you might want to create a session all your own that uses only your own resources and maybe only your own storage classes. You don't have to specify a delegate though, because, as we're going to talk about in a few minutes, there's some asynchronous convenience routines that don't require you to implement a delegate with your NSURLSessions. But if you do specify a delegate, which you need to do for background transfers, then this is the API you use: sessionWithConfiguration passing of a delegate and a delegateQueue. And a delegateQueue can be a concurrent queue.
So, task objects themselves. The session creates task objects. There's a base class task object that has a couple of methods: -cancel, -suspend, and -resume. When a task object is created, it is suspended. You have to send it the resume message in order for it to start working. There are two subclasses, which are really only available for the purposes of providing some sort of semantic glue. Currently, there's a delegate for data tasks, but there's no delegate specific for download-for upload tasks. But if there were in the future, we would have the ability to differentiate between the types.
There's also a download task. And all this adds on top of the normal task object is cancelByProducingResumeData. When a download is cancelled if you use this API, we're able to capture the state of the download and, at a later time, you can create a download task using that resumeData and avoid having to download all those bytes again if the server supports it and if the conditions warrant it and blah, blah, blah. There are all sorts of reasons why that might not work. But if you want it to have a chance of working, this is how you'd get that resumeData.
To create task objects from a session, dataTaskWithURL: You just give it an arbitrary URL-HTTP, MOOF, whatever is going to be supported by the protocols. dataWithRequest, which takes an NSURLRequest, which is sort of the older way of specifying binding a URL with a method or however your protocol is going to interpret that request.
Upload tasks are similar, except that we don't allow you to create an upload task from a URL. You have to create an upload task from a request where you've specified the method like post or put. We give you the option of performing an upload from a file or a data; either of these are great.
But we also give you the option of creating an upload task with a stream-we say "streamed request". That means we're going to ask your delegate for a stream when we're ready to write the bytes of your upload to the network. The reason we have to do that is because sometimes we will open a connection to a server and we'll start writing bytes. And the server will come back and off-challenge us. And we'll have to talk with your delegate. But at that point we've already sent bytes along.
And if we're doing an upload from a stream, we're going to have to throw that stream away and ask your delegate for a new one. If we did the upload from a file or from a data object, we know how to restart that stream without having to ask you for it. If you're trying to do uploads for background transfers, you have to do it from a file because we want to capture that file so we can upload it in the background.
Download tasks are similar. You can download from a URL. I want to copy: I want a URL that goes from, you know, my big file .tgz or from an NSURLRequest or create it from a resumeData block. And that gives you back a downloadTask which you then resume. The lifecycle of a data task starts out suspended, referencing the request that we're going to deal with.
You tell it, "Okay, resume." The state goes to running. And then the delegate starts getting called. didReceiveResponse occurs. It handed you the response, and the response is also available as a property on the task object. As data comes in, will call didReceiveData delegate message 0 or more times.
If the task is in a session that was enabled that has enabled caching, we're going to call your willCacheResponse. This gives you the opportunity to say, "It's for a particular resource," "I don't want to cache this," or you want to modify the cache response, but we're going to call that at this point before we call didCompleteWithError and the state of the task goes to finish.
One thing to note, didCompleteWithError as the final thing that gets sent to your delegate on behalf of the task, the error is going to be nil if the server gave us a valid response. A valid response would be 200 OK. But a valid response is also 404 FileNotFound. Because the body that came back might be interesting to you. You might want to display that body.
The error parameter here then is strictly for transmission errors, like, the host cannot be resolved, or a timeout occurred, or you cancelled the task while it was in flight. When you look at the error here, it's going to contain, well, the reason why the transfer failed, and you should assume that the response and the data that you got back are invalid.
A download task works very similarly. You create a download task. In this case, you reference some file that you're going to get, and it starts doing its work as soon as you say resume-yeah, you send it the resume message. So it goes to running and it will periodically call didWriteData. It will tell you, "Yes, I'm making progress on this download as it's going." Note that this is also what's going to happen for a background download. If this task were created for a background download and your app is running, you're going to receive didWriteData callbacks.
Finally, we've written all the bytes of this file to the file system. We're going to invoke the didFinishDownloadingToURL message. And we're going to give you a file URL in your container. You have to move that to a more permanent location. Move the file to a more permanent location in your container because, when you return from this delegate, we're going to delete the file.
Then we will call didCompleteWithError:nil, because we successfully got the file all the way to disk. Creating NSURLSessionTasks can be done just-you know, throw a request in, get a task out. But there's some convenience routines that are really handy, especially when you have a session that doesn't use a delegate or, I should say, particularly you can only use these with a session that doesn't have a delegate. So you say dataTaskWithURL:completion and you give it a completionHandler.
The task object that comes back has to be resumed. But it doesn't bother calling any of the delegate messages that might be in the session, or it might not. And when the work is done it invokes your completionHandler. This is true for, you know, with an upload via file or data, or a download to disk. In that case, the completionHandler references the file. And, like the delegate for didFinish LoadingToFile, you have to move that file away.
This is what this looks like (the convenience routines). Very simply you just create a URL that you're going to request off the network. In this case I'm going to create a configuration object using the ephemeralSessionConfiguration prototype object. I don't want to store anything and I don't want anybody to know about this resource.
I'm going to create a private session. And then I'm going to create a task with that private session and with that URL. So I tell the session, dataTaskWithURL, "Here's my URL. Here's my completionHandler." The signature for the completionHandler is data, response and error. Error will be nil if data and response are valid.
I resume the task, and then I go back to running in the run loop. If you were to just exit right here, nothing would happen because we're not doing anything. We have to run the run loop or dispatchMain in order for this completionHandler to get called. It gets called and we're able to deal with the data that came back.
The delegate that you create a session with then, is comprised of a number of different protocols. The session level delegate is sort of meta information about the session. When a session is created, it might become invalid. You can send an "invalidate" message, in which case the session invalidation delegate message will be called.
But there's a more interesting delegate message sent to a session delegate, which is for connection-level authentication. If you connected to a website that has SSL encryption, we're going to call your session-level authentication challengeHandler to ask whether or not you want to continue connecting to a site. We're going to put multiple requests on the same socket. We're not going to make a new socket and off-challenge you for each request as they go. We're going to do it once.
On the basis of each task, on each request, there's an NSURLSessionTaskDelegate. You can see that these things are sort of building on top of one another. The most important thing that this delegate responds to is didFinishLoadingWithError. And again, if the error is nil, that means that the response was valid and you should deal with the data. If the error is not nil, then something happened that kept us from being able to transmit your request.
There is also request authentication handling here. If the server says, for a particular request, "I don't know how to deal with you. I need some credentials for this request." It has nothing to do with any other request, just this request. Then that challenge request will come through this task delegate.
Data delegate then extends the task delegate. It's got a couple new methods that are interesting. First, didReceiveResponse. Suppose you're downloading a file off the network or downloading data off the network, and you look at the content type when you get the response back. The content type says that this is a disk image. Well, you probably don't want to read the disk image all to memory.
The completion routine for this delegate message allows you to specify that this data task should become a download task. And that's exactly what happens. We create a download task. We replace your data task and we start writing the file to disk. When the file is completely written, we treat this task as if it were a data task and we notify you of the file's location.
The bytes that are read off the network then are sent to you as didReceiveData callback, zero or more didReceiveData callbacks. One thing to note: you don't have to copy those datas. You can keep track of them. You can reference them. But you don't need to copy them. Each of those datas may, under the scenes, be discontiguous data.
So you should use the NSData discontiguous data application APIs instead of requesting the byte pointer to the start of the data. And that's probably true with a lot of APIs that are producing data today. Behind the scenes, we're trying to stitch things together to keep things discontiguous in memory, which is a big performance win.
Finally the download delegate extends the task delegate. It doesn't receive the bytes, it receives notification of the bytes. In the end, it receives a delegate message that says, "Here is the file location for the data that we just transferred." One quick thing that I want to talk about is the delegate queue that you created the session with. We treated it in iOS 7 and Mac OS X Mavericks as a serial queue. Every time a task had something to do, it would call into your delegate and wait for that to return.
And go on to the next task that had some work to do. The problem with this approach is that, if any one of your delegates stalled for some reason, like is happening here, then we have to wait for that delegate to return before the next task can come along.
In Mac OS X Yosemite and iOS 8, we're now going to treat your queue as concurrent if it is a concurrent queue, as wide as you have specified. So all of these tasks are able to call in to your delegate at once. No matter what order they finish, you know, delegates may finish at different times. We're always going to be able to enqueue work for the next task, and that's a good thing.
There is a little bit of new API that I need to talk about in NSURLSession. This revolves around storage objects like the NSHTTPCookieStorage and NSURLcredentialStorage and the NSURLCache as well as NSURLProtocol. In each of these cases, we found that it was insufficient for the accessors for these storage objects to receive just the URL.
People who were subclassing these objects wanted access to the task that was asking the question. So there's a new category NSURLSessionTaskAdditions. If you look in the headers or on the NSURLLibrary, you're going to see this category show up, and it provides asynchronous gets and, presumably, asynchronous sets, although that's an implementation detail.
We'll take NSHTTPCookieStorage for an example. So two new methods of gets: storeCookies:forTask. Here's an array of cookies, and I'm doing it because this task cares. You look-the implementation will look in the task, look at the current request, get the URL out of that and do that work. getCookiesForTask works the same way.
I would like the cookies for this task. But instead of synchronously responding, here is a completionHandler. Once you've got those cookies, invoke the completionHandler, and you don't have to block me. These APIs are available on NSHTTPCookieStorage, NSURLCredential and NSURLCache. As I mentioned earlier, during the keynote there was a slide which had one little bit of interesting stuff in it. And I'd like to ask Scott to come on up and tell us about the new protocol support.
Thank you, Steve. Hello, everyone. I'm Scott, and today I'll be talking about new protocol support in Foundation Networking. We've been working hard to improve the performance of our HTTP implementation for our developers-you guys. And a popular request at last year's WWDC was to add support for the SPDY protocol. Today we're pleased to announce that the SPDY protocol is now supported by NSURLSession on OS X Yosemite and iOS 8.
Not only does this mean that it's available in Safari for your day-to-day browsing, but it's also available for direct use in your apps. Even if you don't use NSURLSession directly in your applications, keep in mind that SPDY's also leveraged through other Apple frameworks, like UIWebView, which you might use in your apps. For those of you who don't know what SPDY is already, it's a protocol that is designed to make the web faster.
Essentially, what it does is it changes the on-wire format of HTTP/1.1. But the semantics of HTTP/1.1 stay the same. So you know about request types like get, post, put. Those are the same. Response codes, like 200 OK, that's still the same. Caches, cookies, credentials-those are also all the same.
SPDY is also serving as the base for the HTTP/2.0 draft specification. I'm actually going to come back to that point a little bit later on. But it's interesting to see where technology and the sort of development specifications is heading as a result of the SPDY work. Now the way SPDY works at a very sort of high level is that it allows for the exchange of multiple HTTP messages-both requests and responses-simultaneously and potentially out of order, all over a single TCP connection. So if you'd like to use SPDY in your applications, here is what you need to know.
As I mentioned, it's available on both OS X Yosemite and iOS 8. And we support three versions of the SPDY protocol: 2, 3, and 3.1. And what we've decided to do is make it supported transparently by NSURLSession. So this goes along the lines of a philosophy we have. Which is that we will always pick the best protocol we should use when communicating with your server.
Currently, that means we're going to decide if it's going to be HTTP/1.1 or SPDY and the version of SPDY that we're going to use. But this could also hold true in the future for some other protocol. What this means for you is that no source changes are needed.
It will just work. If we look at a source code example here, what you do first, of course, is create your URL for whatever resource you'd be interested in fetching from your server. And then you'd construct a session task. In this case we're using a data task-in this case, also with the shared session-and then having a completionHandler with the data you'd receive, the response and a transmission error if there was one.
Of course, you then have to resume the task to start it. What you'll notice about this code sample is there's nothing different here because SPDY is going to be used. There isn't some code that I added, changed, or otherwise removed to support SPDY. In fact, this example should look very similar to what Steve showed you not more than a couple minutes ago, and that's intentional.
I want to dive into some of SPDY's benefits so you can understand how it will actually impact your apps. The way SPDY works is it has a single, long-lived TCP connection between the client and the server. This actually helps to mitigate latency penalties often times that are seen when you ask for additional resources, because traditionally we may have had to open an additional socket and TCP connection to get the data back.
But now we have the one connection that's open for a longer period of time, which helps to get rid of that latency. It also means that since we're only opening one connection to your server, that any given app instance now should be using fewer resources on your server, which is a big win for you.
SPDY supports the concept of multiplexing, which is where a request and response can be interleaved. Potentially multiple requests and responses can be interleaved over a single connection. This gets rid of what's known as Head-of-Line Blocking. This is where, while you're in the process of receiving the response for one object, that you can't receive responses for other objects on the same connection. For example, you might be in the process of downloading a large image, and that actually then gets in the way of your ability to download say a smaller and more important Javascript file or CSS file.
Finally, SPDY supports the notion of priorities. Now the order in which you issue requests no longer has to dictate the order in which responses are actually received by your application. I want to sort of show you a diagram of how this works. Keep in mind that this is going to be sort of an ideal network where there's no latency and perfect bandwidth utilization. We're going to first start by looking at how Head-of-Line Blocking is present in HTTP/1.1 without pipelining.
Imagine you issue three requests for three resources: an image, a style sheet, and some XML data. The first thing that will happen of course is we'll send out the GET request for the image. And then you'll wait a while as all the bytes in the 200 OK response come back for that image back to your app. Meanwhile, notice nothing has happened for the style sheet or the XML data.
Then we can send out the request for the style sheet, receive its response, and then do the same for the XML data. So you can see here that during the time when you are receiving the response bytes for the image, we can't make any forward progress with the style sheet or the XML data. Now pipelining makes the situation a little bit better, but it doesn't get rid of Head-of-Line Blocking still. So let's see what that looks like.
Now, requests can get sent out one after another. But responses are still blocked by the previous response. And Head-of-Line Blocking is still an issue even if you are using pipelining. So multiplexing changes that with SPDY. Let's look at the same three resources now, but this time with multiplexing.
We'll start by sending the request for the image. That should look no different than before. Like before, with pipelining we can actually send the request for the stylesheet immediately after the request for the image. And of course we can start making progress by receiving bytes for the image. But here's where things get different.
Because the stylesheet is higher priority than the image, the server will start sending back response bytes for the stylesheet instead of response bytes for the image. And then once the request for the XML data has gone out because it is the highest priority object, we receive its response bytes in lieu of the image bytes or the stylesheet bytes.
You'll also notice in this diagram that the amount of data we're receiving for the XML data is a larger chunk than that of the style sheet or the image. That was intentional. Often times, with SPDY implementations, the responses are weighted by priority. So if we let this sort progressing continue, what we end up seeing is that we can receive the entire stylesheet and XML data before the image, even though those resources were asked for after we asked for the image.
So you might be wondering why should I adopt SPDY? And the plain and simple reason is that it can give a better user experience. That's what we're all after I would think. There's sort of two reasons this is the case. The first is that, as I mentioned, it reduces latency by having a single, long-lived connection open between the client and server.
What this means is that over the lifetime of you app, as more and more requests are issued, we don't have to continually open new connections. Which helps to get rid of that latency. It means that your app can have much more interactive behavior even when using a cellular connection. In our own performance measurements, we found that SPDY could be up to 25% faster than HTTP/1.1. And I'll come back to this a little bit later on.
There's also though some more subtle points I want to raise about performance in SPDY and its benefits. Because it's only opening a single TCP connection, it also only has to do a single SSL handshake. And that means that your app will have less CPU use, and over time better battery life on the device.
It also means that when your app becomes the next big app on the app store, and a big hit, that you may not have to roll out as much server site infrastructure to support the increased number of clients, because each client has fewer connections going back to your server, right? It's one instead of many.
I already told you that if you'd like to make use of SPDY you don't need any client-side code changes. But there are a few other points with respect to SPDY adoption I do want to talk about. First, we are planning to add API for setting priorities on session tasks. You'll see that in a future seed; it's not currently available. We'd also welcome any other feedback from you during either of the networking lab sessions, or through a bug report if you believe there's other APIs that would be useful that you don't currently see.
While you don't need to have any client-side code changes to support SPDY, keep in mind SPDY does require server-side support. This all happens when the client is negotiating with the server, using the TLS handshake. The reason this is important is that all of the URLs for requests that you issue from your application need to have HTTPS and not HTTP. I'll just point out that there's a fair amount of existing web server software and content delivery networks that already support SPDY. So you may already have SPDY support and have to do nothing at all to turn it on, on your server.
Finally, many of you might have an NSURLProtocol subclass, or maybe perhaps multiple in your application. And I do want to note that our SPDY implementation is not going to interfere with your protocol subclasses regardless of whether or not they're adding support for SPDY or some other protocol entirely.
I mentioned that we found that SPDY could be up to 25% faster than HTTP/1.1. I want to go into a little more detail about sort of the performance and expectations. Naturally, parallel TCP connections in some cases can be faster than SPDY single connection. What we've found is that whether or not SPDY is going to be faster is dependent on both network conditions and your workload.
In general, our guidance is that if you are going to issue many requests, particularly requests for small size objects, you're going to see a performance gain with SPDY. But if you're writing an app that's downloading a handful of files, perhaps though also downloading large files, such as a movie, SPDY is not going to be advantageous for you.
So keep in mind that you control whether or not SPDY's going to be used based on whether or not it's turned on on your server. I also want to note that the SPDY specification indicates that HTTP headers can be compressed to boost performance. But it turns out this is actually susceptible to the crime vulnerability, which you can read about online. So in our implementation, as is in many common SPDY implementations, we've actually disabled this for user privacy. So you're not going to see a performance boost from compressed headers.
Finally, I want to point out SPDY is not an IETF-recognized standard. But we see it as a protocol that is paving the way for HTTP/2.0. In particular it gives you, developers, the opportunity to get a jump start on using a protocol that relies on a single, long-lived connection with multiplexing, which is the same direction that HTTP/2.0 is heading.
I want to wrap up and talk about some best practices for working with SPDY. Keep in mind, these are not necessarily hard and fast rules that you always have to follow, but these are good suggestions just to keep in the back of your mind as you work with SPDY.
The first is, it's best if you can issue your requests as soon as you want the resources. In other words, what I mean is you might have previously held back and actually manually scheduled requests to try to avoid Head-of-Line Blocking. But because Head-of-Line Blocking is no longer of concern, we ask that you enqueue all your requests right away, and let multiplexing do what it's meant to do.
Also, you might have spread your content across multiple host names, called hostname sharding. So if some resources might have been, like, on css.apple.com, some would have been on images.apple.com and so on. For HTTP/1.1 this made sense. It would cause us to open multiple TCP connections which would boost performance.
But since SPDY relies on a single, long-live connection which gives us optimal connection reuse, it's actually best if you unshard and consolidate all of your content to a single hostname and single port, so we can have that one, long-lived connection to give you the best experience possible. I'd like to turn things over to Dan. He's going to be talking about Background Networking and Extensions.
Good afternoon, everyone. I'm Dan. And as Scott said, I'm going to be talking about background networking. So Steve mentioned before that you can enable background networking in your applications using NSURLSession if you use the background session configuration with identifier factory method. I'd like to give you a brief overview of what I'm going to be talking about today. First, I'm going to go over why you'd want to use background sessions and background networking in your applications. Tying in with this, I'll talk about using background sessions in app extensions, which is a new feature in iOS 8 and OS X Yosemite.
I'll then talk about discretionary networking, which is a feature of background networking that allows us to schedule tasks when it's appropriate, given current power conditions. Finally, I'll talk about using background sessions properly in your applications, going over a couple use cases, and talking about some common pitfalls and best practices.
So why do you want to use background sessions in your applications? Well, the main benefit, particularly on iOS is for multitasking. In a background session, file-based uploads and downloads can continue out of process, even while your app isn't running. This means that your app can crash even. It can be terminated. Or, on iOS, it can be suspended and those file-based uploads and downloads will still continue, and will actually wake up your app on iOS in the background to handle things like authentication challenges and the completion of all the tasks in your sessions.
Another benefit is that in a background session, we monitor the network and power environment for you. This means that we cover things like network reachability and connectivity for you, so you don't have to use the reachability APIs at all. We won't attempt to establish a connection until we know that the server is reachable.
And similarly, if the user is performing a download and steps out of Wi-Fi, normally that task would then fail with a transmission error. But, in a background session, we'll actually recover from that automatically and retry it and resume where we left off if the download is resumable. And you won't hear about that error.
For discretionary tasks, we also perform some battery monitoring so that we don't perform a task if the user's really low on battery and not charging. We'll also do bandwidth monitoring. What this means is that if the user's on a really flaky Wi-Fi network and isn't making really sufficient throughput, than we'll stop and just retry automatically when network conditions are better. I'd like to talk about using background sessions in app extensions, which as I said are a new feature of iOS 8 and OS X Yosemite.
Now extensions are very short-lived processes. Generally they're only going to be running while they're actually active on screen, and that's going to be a very short period of time usually. In-process networking really isn't sufficient in an app extension if you need to do any moderate- to large-sized upload or download.
But if you use a background session, then that task will actually be performed by our background daemon. So your app extension can exit or be suspended and, on iOS, when that task completes or authentication is required, will actually launch the app that your extension's shipped with in order to handle those events.
So essentially, your app extensions and your apps can share background sessions. This leads us to a couple constraints when using background sessions in app extensions. The first is that in order to use a background session in an app extension, you need to use a shared data container. Now the reason for this is that by default an app and any extensions that it ships with are in different data containers and won't have access to the same sets of files. But in Xcode you can create an application group using the Capabilities tab. And if you create an application group, you can tell us the identifier of that app group of that group container, and will download into that container. I'll show you how to do that momentarily.
Another caveat is that only one process can be connected to the background session at a time. This means that if your app is running in the background, and then another app launches and brings up your extension, it won't be able to use that same background session. Now a background session is essentially defined by the identifier that you use when you create your background session configuration object.
What we recommend here is that you use a different background session identifier for your app and for each extension that you ship with. The only time that you should take advantage of the fact that your app and your extensions can share background sessions is when we launch your app to handle events for that session.
Now I mention that you need to use a shared data container when using background sessions and app extensions. You can specify that using the shared container identifier property on NSURLSessionConfiguration. What you do is you just create a configuration object using the backgroundSessionConfiguration WithIdentifier factory method that Steve showed you before. And you set the sharedContainerIdentifier property to the identifier of your shared group container. Then you can create an NSURLSession from that configuration object and create tasks in that session.
Now I'd like to switch gears and talk about discretionary networking, which is something that some background tasks can use in order to perform tasks at power-optimal times. What this means is, that we will take into account things like, whether or not we're on a Wi-Fi network or using cellular data.
And we'll also take into account the current battery state, whether or not we're charging, how much-what percentage the battery is charged. We also take into account things like how often your app is launched. If it's something that a user launches really frequently, we'll be more likely to treat a task more urgently, since the user's more likely to notice when those resources are downloaded.
One improvement that we've made in iOS 8 for discretionary transfers, is that tasks are treated with more urgency as time goes on. In iOS 7, discretionary transfers were limited to Wi-Fi only. Now, while we may limit discretionary transfers to run while there is Wi-Fi and plugged in at first, these constraints will relax as time goes on. As we approach the resource timeout that's specified on your configuration object, we'll be more likely to relax these conditions and allow transfers over battery and cellular data.
There are a couple different ways that you can use discretionary networking in a background session. One way is by explicitly opting in using the discretionary property which is just a boolean in an NSURLSessionConfiguration. This was made available on iOS 7 and is now available on OS X Yosemite.
You might want to do this for any tasks that aren't really user-initiated. So let's say you have, you know, an app that lets users watch episodes of a TV series. And they're watching an episode that they've downloaded already, and maybe you want to pre-fetch the next one so it's ready for them when they're finished watching the current episode. Now, that's not something the user explicitly requested. So that's something that you might want to treat as discretionary.
Similarly, if you have an app and the user is modifying a document, you might want to upload that document to your app servers so that it's accessible from the users other devices. And this might also be something that's discretionary. On iOS there are also times when we will treat tasks as discretionary automatically. This happens when your app is running in the background.
In iOS 7, we introduced a few new multitasking APIs. Most notably background fetch updates, and handling silent push notifications where your app gets a limited amount of time, on the order of 30 to 60 seconds, to run in the background to make updates. When used in-this is really great when used in conjunction with background uploads and downloads. But because the user doesn't know that your app is running at this time, any downloads or uploads that you enqueue will be treated as discretionary automatically because this work can't be user-initiated, because the user doesn't know that you're running.
One improvement that we've made here in iOS 8 is that any of these tasks that we automatically treat as discretionary will become non-discretionary if the user launches the app and brings it to the foreground. I've mentioned a couple times that on iOS we will launch your apps in the background to handle events like authentication and the completion of all your tasks. I want to talk a little bit about how you handle those events.
In iOS 7, we introduced a new method on UIApplicationDelegate, called application: handleEventsForBackground URLSession:completionHandler. When this is called, you'll be provided with the identifier of the session that needs your attention. And at this point you should reconnect to that session by creating a background configuration object with that identifier, and then an NSURLSession from that configuration object.
At this point you'll immediately begin receiving the delegate messages that you missed. Maybe authentication challenges or didCompleteWithError callbacks. And after you're finished handling these events, you'll want to call the completionHandler that was provided to your UIApplicationDelegate. This completionHandler allows us to take a snapshot of your UI to show up in the app switcher, so that it's up-to-date when the user double-clicks the home button. It also allows us to suspend your app. Now, when you're launched to handle events for background sessions, you'll again be given a limited amount of time to run in the background-around 30 to 60 seconds.
If you don't call your completion handler within this time, your app will be terminated, meaning it won't have an up-to-date snapshot and it will be slower to launch the next time the user wants to launch your app. So it's important that you call this completionHandler so your app gets suspended and has an up-to-date UI. Once you're finished receiving all the pending events from your NSURLSession, we'll deliver the URLSessionDid FinishEventsForBackground URLSession message to your NSURLSessionDelegate. And this is an indication of when it's a good time to call your completionHandler past your UIApplicationDelegate.
I'd also like to talk about using data tasks in background sessions. Steve mentioned earlier that data tasks, instead of downloading to a file or on disk will just deliver didReceiveData callbacks in memory. Now data tasks were unavailable in background sessions in iOS 7 and OS X Mavericks. But they're now available in iOS 8 and OS X Yosemite with one restriction, which is that we will only perform a data task in the background session while your app is actually running. If your app gets suspended or is terminated, then there is no one to deliver that data to, so it makes no sense for us to continue performing it.
However, you can convert it to a download when you receive the response. Steve eluded to this earlier when talking about the didReceiveResponse callback. That delegate message provides a completionHandler. And you can pass NSURLSessionResponse BecomeDownload to that completionHandler and then we'll start streaming the bytes to a file on disk rather than memory. Once you do that, this becomes a download that can be continued even after your app is suspended or exits.
Now I'd like to talk about a couple common pitfalls that we've seen from apps in the past when using background sessions. One common pattern that we've seen that we'd like developers to avoid is a pattern where they create one task at a time. So you might, for instance, be downloading a large video from your server that's split up into many different smaller segments, each a separate file.
If you download all of these and if you, let's say, create a download task for the first task for the first asset and then create a second download task for the next asset once the first one completes, this is a really bad pattern, particularly because, if the user suspends the app by going to the home screen at any point, than you'll actually need to be re-launched in the background once the current download finishes before you can enqueue your next one.
This means because it's running in the background that it will automatically be treated as discretionary, which means we won't guarantee that it will start right away. In particular, even if conditions are great-we're 100% charged and connected to a power source and we're connected to a great Wi-Fi network, we still make no guarantees that we'll start right away. In particular, the system will take measures to prevent your app from being launched too frequently. So you can't rely on launches for background sessions for any sort of regular launch events.
Tying in with this, it's much better if instead of downloading lots of small assets like this, if you can zip these up on your server into one large zipped asset. That's much more efficient for a background download. And finally, blocking while waiting for transfers to complete is just a really bad idea when doing any kind of networking. But it's particularly bad in a background session where we recover from network failures automatically and don't tell you about them and, again, for discretionary transfers where we're not guaranteed to start right away.
I'd also like to go over a couple best practices for using background sessions and also NSURLSession in general. One common mistake that we think some people have made in the past is that they've assumed that when running in the background to handle things like a background fetch update or a silent push notification, that they've been required to use background sessions. Now, using background uploads or downloads with these forms of multitasking works really well, but in particular for large downloads.
When you're running for a background fetch update or a silent push notification, as I said, you'll have about 30 to 60 seconds to run in the background. Now if you have any small networking tasks that could finish within this time, it's perfectly okay to do them in an in-process or default NSURLSession.
If you have something like a Twitter client that's going to be downloading a few tweets for a background fetch update, that's totally doable in process. On the other hand, if you want to download, you know, the next episode of a TV series, that's something that's fairly large and probably not going to finish within the 60 seconds you have to run-that should be done in a background session.
It's also important that you support resumable downloads on your servers. As I mentioned in a background session, we retry automatically after network failures if the user goes off of Wi-Fi, for instance. So if you support resumable downloads, we don't have to re-download bytes we've already received. But if you don't, than we have to start from the beginning. Now supporting resumable downloads is generally just as simple as supporting Range GET requests on your servers. And most servers should support this configuration out of the box.
Finally, it's very important that you handle launch events properly, as I talked about before. This means when you get launch to handle a background session, you should reconnect to that background session and handle any messages. This is particularly important with authentication. Because if you don't respond to an authentication challenge, then that task will timeout and fail. And that's not what the user wants. Finally, be sure to call the completion handler that's past your UIApplicationDelegate. As I said before, this allows your UI to be up-to-date in the app switcher and makes it so your app is suspended after it's completed instead of being terminated.
Before I wrap up, I'd just like to give a brief summary of what we discussed today. First we talked about new APIs and NSStream and NSNetService. We gave a review of using NSURLSession from what we talked about in last year's WWDC session. We talked about SPDY and new protocol support. And went over some new features and best use cases in background networking.
In case you don't get the chance to ask us any questions in person this week while you're here, Paul Danbold is our Technologies Evangelist. And he's a great person to ask for any questions that you might have that you don't get a chance to ask us while you're here. We also have plenty of documentation available at developer.apple.com, including for NSURLSession. The Apple developer forums are another great place to ask questions that you don't get to ask while you're here.
I'd also like to point you at a couple related sessions that are going on this week. In particular you might be interested in the Extensions sessions. There are two of them. You can learn more about creating app extensions, and we'll also talk a little bit about using background sessions and app extensions. With that, I'd like to thank you all for coming. And we look forward to seeing the amazing apps you create with iOS 8 and OS X Yosemite [applause].