Core OS • iOS, OS X • 55:57
Blocks, Grand Central Dispatch, and XPC form a powerful foundation for asynchronous processing and communication in your app. Dive into the basics around blocks, GCD, and XPC and learn about their improved integration with ARC. Discover the common design patterns used when writing asynchronous code to offload work from your main thread, perform IO in the background or send messages to other processes, and how to apply these same patterns to your own code.
Speakers: Daniel Steffen, Kevin Van Vechten
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good morning and welcome. Thanks. Welcome to Asynchronous Design Patterns with Blocks, GCD, and XPC. My name is Kevin Van Vechten and I'm the manager of the GCD and XPC team. And today we're going to start off with a bit of an overview of blocks, GCD, and XPC for those of you who might not be familiar with the technology. And then we're going to dive into some detail about what's new with Objective-C and ARC in GCD and XPC. And then finish up the talk with a lot of discussion about how to use these technologies to implement asynchronous design patterns in your code.
So first, let's get started with an introduction to blocks. The best way to think about blocks is by first thinking about function pointers in C. Blocks are really anonymous functions in C. So here we have a type declaration of a callback function. It takes a string as an argument.
It doesn't return any value. And we can turn this into a block type simply by changing the star to a caret. And the caret is a special character the compiler recognizes to indicate that this is a block type. But otherwise, it looks the same as a function pointer.
So we can use these blocks in a much different way than we typically use function pointers, and we can actually declare a block variable. So we have our type, callback block. The variable name is b. And then instead of just giving it the address of a function to call, we can actually give it the body of the function right there in line in our code. We declare the arguments, which in this case is a string, and then the body of code prints that string out.
Calling the block is really the same as invoking a function pointer in C. All you need to do is reference the block variable by name and pass it the arguments. So in this example, if we were to compile and run this code, it would just print "Hello, world." So declaring the body of code in place right where you need it is really the key benefit of blocks. With function pointers, you need to define a function that's a helper somewhere else in your code.
You need to make changes there and wherever you use it, you know, if you need to renegotiate how you pass data to that code. But with blocks, since you declare them directly in line, they actually can make reference to variables in the enclosing scope. So if you have a big function and then you declare a block in it, any variables outside of the block in the function are actually visible to the block.
And we'll go into detail in a moment to see how that works. And even further than just referring to these variables, you can actually modify variables in the enclosing scope, which is also very powerful. So let's take a look at these three benefits of blocks in the context of three very common types of blocks: completion, comparison, and enumeration.
So completion is something you probably are all familiar with to some extent. There's typically a pattern where you tell the system to perform some activity on your behalf, and then it calls a function that you provide when it's done and passes the result of that operation. So we might have a completion callback function, and we can turn that into a block.
And then let's say we've defined a routine ourselves or maybe we're making use of an API call that looks like this where it downloads some resource asynchronously from the network. All we have to do is provide it a URL and then we provide it a completion block that gets called when the resource has been downloaded and that passes us back the data that was downloaded.
Well, we can combine these two together and write a very simple routine that, say, updates an image in one of our views based on the data downloaded from that URL. So you can see we're just downloading data from the URL. When that operation is complete, we've provided a completion block. We get past the data. We can turn that data into an NSImage. We can update our view, and then we're done. So the key here is we're declaring it in place. We don't have to define an external function and refer to that.
Our next example is comparison. You've probably all written a comparison function at some point. To look at two values, decide which one is greater or less than the other value, and maybe you would use this in some sort of sort operation. So we're taking two arguments, returning a comparison result. We'll turn that into a block.
And we'll assume that there's some sort API on the system or some function that we've defined that sorts the contents of a mutable array. In this example, we're going to have a mutable array of string objects, and then we're going to sort them in some way. Well, we can very easily define a helper routine that sorts all the strings in this array alphabetically. And all it needs to do is call the compare method on one of the strings to compare it against the other string and return the results. And then at the end of the call, all the elements in the array will be sorted.
But we can expand this a little bit and really make use of blocks by referring to a variable in the enclosing scope. Let's say we want a more generic sort operation. We want to be able to sometimes sort case-sensitively and other times sort case-insensitively. Well, we can take an argument to our helper routine and turn that into the right option flags for the compare method of NSString. And we can do that in the calling function.
And at the time the comparison block is passed into the sort routine, It has reference to whatever option flags we specified. So we've really taken our completion callback and made it very generic, very simply. If we were to try to do this with function pointers, we would probably have to define some sort of context structure and get the data into the context structure and pass it along, and then, you know, do a bunch of casting and make sure everything's all in sync. But with blocks, we can just refer to the variable exactly where we need it.
And finally, let's look at the example of iteration. Pretty common pattern. You know, perhaps you have a lot of data. In this case, we'll talk about numbers in a set. And we just want to enumerate every item in the set and apply some function to it. We can turn our Applier function into a block. And we can assume that there's an API on the system or something that we've written that does the iteration of all the values in the set and applies our function to each of those values. And so we'll use these two concepts to implement something that's pretty powerful.
We will have a helper routine that returns the maximum value that we see in the set. And we can do this very easily by making use of the fact that blocks can modify the variables in the enclosing scope. Now, you'll notice in this example our result variable has this "under under block" keyword attached to it.
By default, all the variables that are referenced by blocks are constant read-only copies of the variable. And that's done for performance reasons. The compiler has to do a little bit extra work if you want to make the variable modifiable. So we don't do it by default. But when you apply this keyword to the variable, you're telling the compiler that you do wish this variable to be modifiable by the block. The compiler does the extra work that's needed to make that possible.
And so here we have a result variable that can be modified by our callback block or our applier block. And we'll start off with the best guess value. You know, let's say it's the smallest number we can possibly represent. So whatever maximum we find is probably going to be bigger than that. And we apply our block to all of the numbers in the set.
The supplier function calls our block once for each number. Within the block, we can look and we can see is this new number we're looking at greater than the previous best guess that we have for the maximum. If it is, we can just update that variable. It modifies it in the enclosing scope. And then when the apply function is done, we're left with the most recently updated version of the result variable, which now contains our maximum value, and we can simply return that.
So now I'd like to move to talk about GCD. GCD takes blocks and it treats them like data objects and lets you do very powerful things with them. So, GCD is short for Grand Central Dispatch. And what it fundamentally lets you do is take blocks and enqueue them on queues for later asynchronous invocation by the system.
And we provide a thread-safe enqueue operation, so you can have any number of threads in your application and they can all be operating on the same queue simultaneously. You don't need to do any additional locking. And once the blocks have been enqueued, the system is then responsible for asynchronously dequeuing the blocks from those queues and invoking them on your behalf.
So before we get started talking about the invocation aspect, I want to clarify the particular type of block that GCD uses. We refer to them as dispatch blocks, and they're a special case in that all of the blocks that GCD uses take no arguments and have no return value.
And the reason we do this is because we can treat every block of code completely identically if we know that the signature has no arguments and no return value. And we can rely exclusively on capturing variables in the enclosing scope as the way to get data into your block.
And it turns out that this is a very elegant approach. And in fact, these specialized blocks that don't have any return value and don't take any arguments can actually be written with the syntax that you see on screen. You can just use the caret and then an open brace, and that begins your block declaration.
So we have dispatch blocks. Now let's talk a little bit about the dispatch queues. Creating a dispatch queue is a very straightforward operation. We have a create routine. It only takes two arguments. The first argument is a label that you provide. It can be anything you want. We recommend the reverse DNS style naming scheme. And we actually take these labels that are applied to queues and we print them out in things like sample or crash reports. So when you're analyzing your code, you can match up what activity is corresponding to which queues you've defined.
And the second argument is really the type of the queue. And there are two main types of queues. There's a serial queue, which executes blocks that are enqueued on it one at a time serially. And there's a concurrent queue, which can potentially execute blocks in parallel at the same time. So in this example, we're creating a very simple serial queue, the basic building block of GCD.
And once we've created the queue, we're going to print out a message that says we're before this dispatch async call. Now, Dispatch Async is a really fundamental piece of GCD, and what it's doing is it's enqueuing a block for asynchronous execution. So it takes two arguments, the queue that we want to target and the block that we want to enqueue. And all this does is it puts the block onto the queue and returns immediately. It doesn't wait for the block to execute. That'll be done later by the system.
And after we've called dispatch async, we print out a little message that says that we're after this dispatch async call. Now, what's really important to note is when we run this, the order is going to be a little bit different than what you might expect. We'll actually see before async, after async, and then later the hello world that was in the block. And again, the reason for this is that dispatch async simply enqueues the block and returns immediately. It doesn't run it yet.
So in our code, we've printed out before async, we've enqueued it, we've printed out after async, and then at some later point, the system pairs up that queue with a worker thread, dequeues the block from the queue, invokes it, and that's when we see hello world printed out.
So these dispatch queues, I talked a little bit about the serial queue, have some interesting properties, the first of which is that a serial queue always processes blocks in strictly FIFO order. So if you submit multiple blocks to the queue, the first one that's enqueued is going to be the first one that's run, and that's a strong guarantee that the system makes.
And that turns out to be very useful in that if you have multiple threads enqueuing onto a queue, the FIFO serialization that's done helps you synchronize your code and kind of provide strong ordering guarantees. The second interesting property of these queues is that we have an atomic enqueue algorithm.
So you're not going to have threads that are waiting on a lock to get their opportunity to enqueue a block onto the queue. And it's, you know, some sort of highly contended resource. It's actually a very efficient algorithm, and you can have many, many threads enqueuing onto a queue, and they're all going to atomically get their block enqueued and return immediately. It's a weight-free algorithm.
And then, of course, as I've already mentioned, the automatic dequeue is the third very interesting property of these queues. The system is responsible for creating worker threads to process the queues, and it then takes... takes these threads and dequeues the blocks and invokes them on your behalf. We'll talk a little bit more about why that's so important in a moment. So here's a little animation of the automatic dequeue process. In this example, we have two queues. They're both serial queues.
One of them has two blocks. The other has three blocks. And we're going to start running the simulation, and you'll see that the system is going to take the first block enqueued to one of the queues and start running it. And when that's finished, it'll move on to the next and move on to the next.
Now, what's interesting is if another thread becomes available to process additional work, we'll then pick up the next queue and start running blocks on that queue. And so even though we've satisfied that strong FIFO ordering guarantee for each individual queue, you can see there's also the opportunity for two queues to be processed concurrently in parallel. So why are asynchronous blocks so important? Well, the key to asynchronous blocks is that they allow you to easily wrap certain pieces of code for execution asynchronous to your main thread.
And this is really important because the main thread in your application is what's responsible for running the main event loop. And the main event loop is what's receiving and responding to touch events or keyboard events or things like that. It's what keeps your application responsive to user input.
And so if you do some really long-term work on this, you'll see that the main thread is responsible for running the main event loop. And the main event loop is what's receiving and responding to touch events or keyboard events or things like that. It's what keeps your application responsive to user input.
And so if you do some really long-term work on this, you'll see that the main thread is responsible for running the main event loop. And so if you do some really long running operation on the main thread, let's say you're just waiting for some file to download from the network or something like that, all the time that's spent in that operation if it's being done synchronously is time that is not being spent receiving events from the user. And so ultimately it makes the app appear unresponsive. On OS X you see the spinning cursor to indicate that an app is unresponsive. On iOS it just doesn't respond to touch events.
And of course if you do this long enough on iOS at some point the app is terminated and you return back to the home screen. So it's really important to keep the main thread responsive to all these UI events. And asynchronous blocks are a great way to do this.
Now, if you're going to take a bunch of work and get it off of the main thread to run in the background, you also at some point need to take the results of that work and get it back to the main thread to be able to update your UI and communicate with the user. And we can do this very easily with something that we call the call callback pattern.
So let's go back to our previous example of updating an image view with some resource that we've downloaded from the network. And depending on what our view is doing, you know, you may have read in documentation or experienced in your own debugging that really UI elements in interaction with the UI should all happen from the main thread. And this is both for, you know, real low-level thread safety type issues and also you want things happening on the main thread just so it's easy to keep all of your view state in sync with incoming events from the user.
So in our example before, we had an asynchronous download routine that as soon as it downloaded something from the URL, it called our completion block. Perhaps we don't want to just set the image of that view right here in the completion block because we're not really sure if the completion block is going to be called on that background operations thread that was doing the download.
So we can add a little bit of code and implement the call callback pattern to get that image update back to the main thread. And we're using our dispatch async routine that enqueues a block for asynchronous execution. And we're using a second routine called dispatch get main queue.
And this is a special routine that always returns a reference to the main queue of your application. And the main queue is defined to correspond to the main event loop and the main thread of the application. So it's a very convenient way to get blocks to execute in that context.
And so the reason it's a call callback pattern is the outer block is the initial call that's happening. And then inside of it, we can repeat the same approach of a dispatch async with a nested block. And that's our callback. It's also important to note that this code is correct. It's okay that the view object isn't retained or released in this inner block. Blocks actually automatically retain and release all Objective-C objects that they reference, even when not compiled in arc mode.
So I'd like to extend on this call-callback pattern a bit and talk about XPC. XPC is a technology that's available on OS X, and it gives you a very simple interface to look up a service by name. And once you've looked up a service, you can send and receive messages to it asynchronously.
And the important part about these services is they're actually separate processes outside of your application's process. So it's a completely different address space, a completely different process managed by the kernel. And we leverage all the blocks and queues in GCD to deliver these asynchronous messages simply as blocks submitted to queues that you provide. So it makes the asynchronous nature of this communication very easy to work with in your application.
[Transcript missing]
But you might have a little helper that needs to do some network activity or interface with certain hardware. And so you can compartmentalize all of that higher privilege code in its own service and apply the principle of least privilege to your design. So let's look through the sample code of working with an XPC service and how it implements the call-callback pattern.
First, we create a dispatch queue and an XPC connection to talk to our service, and these two work as a pair. We can look up our service by name. In this example, it's com.example.render. And we're going to build on our previous example of decoding some image data and assume that the data we download from the URL might not be in a format that NSImage can parse. And so we're going to do some transformation on that data first in an XPC service and then try to make an image from it.
Once we've created the connection, we set an event handler on it. The event handler gets called when there's a reply from the service. We'll do a superficial check to make sure the reply isn't some type of error, but it's real data that we're getting back from our service.
Then we can extract data from the reply. It's really just a dictionary, property list style dictionary. We'll get the data out, and then we can call our completion callback with the data. And the completion callback we had before is the one that actually takes this new style of data and turns it into an NS image.
Once we've fully configured our connection by setting the event handler, we need to call xpc_resume, and that lets the system know that we're ready to start activity on this connection, we're prepared to receive events from it. And then interacting with the service is really as simple as creating an XPC message dictionary, adding some data to a key in the dictionary, Sending the message to the service, and it's at this point when we actually send the first message that the system launches the service if it's not already running, but it's done completely transparently. And then when we're done, we can release the message.
So to go into more detail about Objective-C and blocks, I'd like to invite Daniel Steffen onto the stage. Good morning. So let's dive right into blocks and Objective-C. We've already seen how you can declare a block indirectly in your code in line. When you actually do that, what happens is that there is an Objective-C block object created on the stack. Blocks start out on the stack for performance, for speed, but they are copied to the heap in many cases. For instance, when you are scheduling them for asynchronous execution with GCD, because in that case, they need to survive the lifetime of the current scope.
So what happens to the variables that are captured by a block when you do that? As we've already mentioned, scalers are const copied to the heap. Objective-C objects are retained, but any plain C pointer is just copied as a value, and its underlying storage is not managed for you. So let's see an example of where this can trip this up.
Here we are implementing a block. We have a method that will do some asynchronous work on behalf of the caller and call them back on a queue that they provide by invoking a method on the object that they give us. And we implement that by doing a dispatch async in our method to a queue internal to our object and do some asynchronous work and perform the callback to the caller-supplied queue.
Now, this code looks just fine, no obvious errors, but it turns out that it crashes. So let's see why that is. So here we've represented the two areas of memory important in this problem, the stack and the heap. And when we enter our method, we start out with the two pointers to the objects that the caller passes us and their underlying storage on the heap. Imagine that they start out with reference count one. Now we create this callback block on the stack, which captures the Objective-C object and the block that will execute asynchronously, which in turn captures the dispatch queue. and the callback block.
When we do dispatch async of this block, as part of that, the block gets copied to the heap, and because it captures the callback block, that block also gets copied to the heap. Because that block in turn captured the Objective-C object, this now has its reference count incremented because it's being retained by the block's runtime.
But note that the dispatch queue still has a reference count of one. That is because the dispatch queue T-type there is just a plain C pointer type. And of course this is where the problem occurs. When our method returns and the caller releases the references to these objects, the storage for our dispatch queue will get deallocated, and now we have this dangling reference and the block will crash when it gets executed.
How do we solve this? We have to manually manage the lifetime of the queue object that's passed into this method. We need to add dispatch retain and dispatch release calls. We retain before we async the block and release once we are done with the queue object inside that block. So now I'm very happy to announce that we've improved on this situation in iOS 6 and Mountain Lion because GCD and XPC objects are now Objective-C objects. Thanks.
So back in the same situation, the incoming object, QObject that's passed to us is now an Objective-C object, so the blocks runtime takes care of managing the lifetime for us and we can get rid of these retain release calls again. And everything works correctly. So automatically being retained released by blocks is one big benefit of these objects now being Object-DC objects.
There's many others. You can also use them directly as add property retains now. Previously you had to use add property assign and manually manage the property storage. Now the Object-DC runtime will do it for you. You can also use these objects in foundation collections directly, so you can make an NSRA of dispatch queues, for instance. The static analyzer understands more about these objects, and instruments in the debugger support them better via their generic support for Objective-C objects.
Of course, another area where we've improved the Object-DC language last year with the introduction of ARC in iOS 5 and Line is reference counting. And so here's a typical example of a method where we have to create a bunch of temporary objects to send a message to an XPC connection, and we have to be careful to release all these objects manually at the end of the method so as not to leak any code.
When you compile this code with ARC now, there's no more need for these release calls at the end of the method. The compiler manages the storage of these objects for you. So when you migrate GCD and XPC code to ARC, you can see that the compiler is able to store You can just use the Convert to Objective-C ARC menu item in the Xcode refactor menu.
which will run the ARC Migrator and remove these retained release calls for you and do all the other work of converting your code to ARC. Note that it's even possible to do this again when you've already migrated your code to ARC in the past and it will do the extra work required. Or you can just remove these calls yourselves, of course, and in fact you have to because like calls to the retained release Objective-C methods, these now cause compiler errors when you build with ARC.
What are the requirements for this support? Of course, you have to build with the Objective-C or Objective-C++ compiler, and you have to have a minimum deployment target of iOS 6 or Mac OS X.8. If you still need to support older releases and supply an older, lower deployment target, you will be automatically opted out of this support.
Or you can opt out manually if you need to for some reason by passing OS object use option C equals zero in your preprocessor flags. There's a couple of special considerations when... doing this conversion to Objective-C and ARC of your GCD and XPC code. These are many things that the automatic migration doesn't handle for you, but look at blocks and return cycles and APIs that expose interior pointers.
Let's look at a general example of how you can set up a retain cycle in an object with a block. Here we have declared a class that has two properties, an integer value property and one of these dispatch block type block properties that we've seen earlier. And we've represented the storage of that object on the heap with an initial reference count, retain count of one.
Now we declare a setup method that assigns a block to our block property. And as part of this assignment, we'll have to copy this block to the heap because obviously it has to survive the lifetime of the scope of the setup method. And as shown here, the block captures the value instance variable of our object. And because this is an integer, we expect this to do a const copy of that integer value.
But of course, that's not actually what happens. If you access an instance variable in an Objective-C method, the compiler transparently indirects that by itself for you. And what actually happens is that the block now captures self. And because self is an Objective-C object, the block increments the reference count of self. And now you have a retain cycle. When you get rid of this object, it will not disappear because it retains itself via that property. So how do we break such retained cycles? We'll look at three ways via scoping, programmatically, and with compiler attributes.
So, same situation as before. We've made the capture a bit more explicit, but we still have this retain cycle. How can we break this with scoping? We simply set up a local variable where we put the value that we want to use from the object into before we set up the block and capture that value instead rather than the instance variable or the property, and so we avoid the capture of self completely.
Of course, this behaves slightly differently from before because the value, the property is actually read out much earlier when we set up the block rather than when it runs. Because this is a read-only integer here, this presumably doesn't make any difference in this code, but this is not a solution that may be generally applicable if you have this type of issue. So an alternative is to break these cycles programmatically.
Here we are, same situation we have, but we've changed the example slightly to use a captured Objective-C object property where we presumably can't apply the previous idea. So here we'll set up a manual cancel method that simply nils out the block property. By doing that, we release the previous value that was stored in that property, and so it releases its reference that it had on itself and thus breaks the retain cycle.
And this is in fact exactly what we recommend that you do. When you encounter retain cycles in your use of GCD or XPC API, that has block properties. Here we have two such objects, the dispatch source and the XPC connection. Most of these have event handlers that are block properties of these objects, and it's very common to use the objects themselves in those blocks. So in both of these cases, we've set up retain cycles on these objects and need to break them.
And you do that by calling the dispatch source cancel or the XPC connection cancel APIs. These are previously existing APIs that were optional because the final release would do this cancellation for you. If you have in fact one of these retain cycles, then you now have to call these methods. And of course, if you convert to Arc, the release calls at the end here will get removed.
The third way is to use compiler attributes. If you build with ARC, you can use the under_weak compiler attribute to set up weak references. And here we use this in our setup method to have the block in our object, rather than capturing self, it captures a local weak reference to self.
And because it's a weak reference, the block capture doesn't cause an extra retain count to be taken on the object, and so we don't have this retain cycle. But because weak references are zeroing weak references, inside the block once we run, we now have to check, or maybe we... We may want to check that the weak self is actually still pointing to the object, that the object is still alive when we run.
And we do that by setting up a strong reference inside the block and checking that that succeeded by checking for nil and then doing our asynchronous work and maybe, if not, doing something else. for many more details on under under weak and ARC in general, please go to the adopting automatic reference counting session later that is being repeated later this morning. Our next topic is interior pointers and APIs that expose interior pointers to objects, to container objects.
[Transcript missing]
How do we solve this? One option is to add the Objective-C precise lifetime attribute to the container object that needs to stay alive. And that tells the compiler that this object should stick around for the whole duration of the surrounding scope, exactly the duration of that scope. So what the compiler will do is insert its release call at the end of the scope right before the close curly brace.
And this makes it safe for us to access the interior pointer inside this method now. So in our next section, we'd like to go over some asynchronous design patterns with you that are very concrete steps that you can apply to your code to make it more asynchronous and some best practices to avoid some common trouble spots along the way and show you some ideas of patterns that you can apply to your own APIs when you design your apps or to Apple's APIs. And as a guiding example throughout this section, we'll use an imaginary image of your application that can display images stored locally on your device or retrieve them from other instances of the app on the network and display them.
And step one in this set of design patterns is the most important thing to always remember when you're writing code on iOS or OS X is don't block the main thread. We've discussed this already. The main thread is what handles the user interaction and the UI, and because we want responsive UI in our apps at all times, that's really the only thing that the main thread should be doing. Anything that is CPU-intensive or blocking should be running elsewhere in your app. And running elsewhere, you implement that by running in the background with GCD and blocks.
So here, in our image viewer application, we are on the main thread. We need to do a CPU-intensive render operation for some males, and we want to move that to the background because this could affect the interactivity of our application if we ran this on the main thread. So we pick a dispatch queue that we want to run this code on. Here, we just use the global default priority dispatch queue. We enclose this work in a block and dispatch async it to our queue.
And in the call callback pattern that we've seen earlier, once we're done inside this block with the work, we need to update the UI, so we enclose the updating of the UI in a block and dispatch async that back to the main queue. So when you apply this pattern into your code, it's important to not block too many background threads.
Here we have almost exactly the same example as before, except that we're now doing some IO on the background thread with the NSData DataWeek contents of URL API. We're reading some file in from the file system. Now, one of these dispatch asyncs is just fine. This doesn't cause any problems.
That's in fact exactly the right thing to do. But imagine that we now enclose this whole thing in a big for loop that iterates over all the images that we have stored in our application, which could be many hundreds. Now your app could run into a problem. Let's see why.
So this is an illustration of the single case where we async one of these blocks and run it on our queue and an automatic worker thread comes up and now it's blocked in IO for a while to bring the data in from storage. Now the thing to realize is when you block these automatic worker threads in your apps, after a certain amount of time, the system will create additional threads if there is still pending work to be done so that the app can make progress. But now if you block these additional threads that come up with more IO, this pattern will continue and you will end up with more and more and more threads that are all blocked.
And of course, this can now lead you into trouble. You could deadlock your whole application by having too many worker threads blocked like this and hit the limit of threads that the system allows you to create. Or all these threads could end up using so much memory that your app gets killed, on iOS anyway.
So what's the solution? One option is to use dispatch IO, an asynchronous IO API that we've introduced last year in line and iOS 5 to do this asynchronous IO differently. So here we have the same for loop but instead of doing a dispatch async of a blocking IO API, we use the dispatch IO subsystem to perform asynchronous IO. Here we create an IO object from one of these zero paths and we can submit an asynchronous IO read operation and ask it to call us back on the main queue with the results.
And this will now take care to, even if you submit hundreds of these requests, to all do them, perform them in a way that doesn't overwhelm the system. And for many more details on Dispatch.io, please see the Mastering Grand Central Dispatch session from last year's conference on iTunes.
Our next step is how to integrate with the main run loop when you're writing GCD code. You've already seen the Dispatch Get Main Queue API quite a bit. This, in fact, returns a serial queue to you that cooperates with the main run loop. Every time the main thread goes through its event loop, it will look at the serial queue and perform any blocks that are present on it at that time. So you can be sure that any block that you async to this queue will run in the context of the main thread's event loop.
But of course, we also have many run-loop based APIs on our system. And APIs that have run-loop based callbacks. Here I'm thinking of things like NSTimer, any of the perform-mit-selector APIs, anything that has an asynchronous delegate call. All of these APIs schedule a callback on either a specifically selected run-loop or the current thread's run-loop. And it is important to be aware of which thread that run-loop belongs to when you use these APIs.
In particular, typically you shouldn't call these on the automatic worker threads that GCD provides if they're going to schedule a run-loop callback on the current thread's run-loop. This is because you don't have control over the lifetime of these automatic worker threads and their run-loops are typically not running anyway.
So the usual solution is to call such APIs on the main thread, but then when you get a callback from them, be aware that you are now still on the main thread. So don't block there if you have to do any blocking or CPU intensive work, apply step number two there.
So let's look at an example of this in our image viewer application. We'd like to download some pictures from a remote instance of our app. We have saved the name, the Bonjour name of that previously. And here we use the NSNetService API to resolve that name to an address that we can connect to. And this API schedules a run loop callback on the current thread's run loop and calls the NetServiceDidResolveAddress delegate method when it's done.
So if you do this on the main thread, this works perfectly fine. But because this is a network operation that could potentially block, we want to do the right thing and dispatch async this into the background. And now we have a problem. The run loop method, the run loop callback doesn't get called anymore. And let's see why this is.
Here we start out on the main thread with its main run loop running. We async this block to a background thread, to a queue, and we run on an automatic worker thread, and it now schedules its run loop callback on that automatic worker thread's run loop. This run loop isn't running, and worse, when the block finishes, that thread will probably get recycled by the system, and your callback along with the run loop disappears, and then it gets called.
So the solution here is to continue calling such API on the main thread. It's already asynchronous. It doesn't block your thread, your main thread, so this is safe. But then inside its run loop callback, that's where you should be careful to not block and dispatch async into the background if necessary.
The next pattern we'd like to look at is using one queue per subsystem. This is an idea to subdivide your app into independently operating subsystems that are controlled with serial dispatch queues and interchange messages with dispatch async. And in this architecture, you can think of the main queue as simply the access queue for the UI subsystem.
So here we've represented our app, architected in this fashion graphically with the UI subsystem in the top left corner, a networking subsystem, an image processing subsystem, and the storage subsystem, all of which can operate independently. And now they can exchange messages by dispatch asyncing blocks between themselves and queuing them on their controlling serial queues. And typically, using this architecture gives you a very good granularity of asynchrony in your application.
So let's look at a very simple example of how we would do this in code. We're back in our net service, the resolve address, run loop callback on the main thread. Now we want to do some network operations, so we go to our networking subsystem with a dispatch async to its computer. And we go to our storage subsystem to store that data by dispatch asyncing to its controlling queue, the store queue.
Once you have that, we go to the rendering subsystem by dispatch asyncing to the render queue. And once we have finished our rendering operation, we go back to the UI subsystem by dispatch asyncing to its controlling queue, the main queue. Of course, this is a very simplified example in your code. These asyncs probably wouldn't all be inside the same method, but the same idea applies.
And when you're implementing this kind of architecture, you may have subsystems that have reader/writer access, that support reader/writer access. And you can improve performance of these by using a concurrent subsystem queue. This is a type of queue that we've introduced last year in iOS 5 and Line that support multiple concurrent operations. And you create these with the same dispatch queue create call but pass in dispatch queue concurrent as the second parameter.
And once you have one of these queues, you can implement multiple synchronous read operations by doing a dispatch sync onto that queue. Multiple of these dispatch syncs can occur at the same time without interfering with each other, so you can have multiple concurrent readers. If you have to do a write operation, you will use the dispatch barrier async call. This will ensure that because it's a barrier, it is the only call, the only block that's accessing the subsystem during a write operation.
Let's look at a very simple example of-- oh, before that, we went into much more detail on this in the Mastering Grand Central Dispatch session last year. Let's look at the quick example of this in code. Here in our storage subsystem, we create its controlling queue with a DispatchQueueCreate call by passing in the DispatchQueueConcant flag that I just mentioned, and now implement a read operation to the subsystem with DispatchBarrierAsync.
Once we are inside this block, we are sure that we are the only ones on that queue at that time, so we can safely mutate our storage at that point. But now in our rendering subsystem, we need some image data to render the thumbnail. This is where we would do a read operation from the storage subsystem, so we use a dispatch sync call to our store queue.
And multiple of these could now occur in the application at the same time and operate very cheaply by just doing a direct check for any presence of barrier operations on the queue, and if not, doing this block inline directly. And we extract some data from that block with the under_under block syntax that we've seen earlier.
Our next topic is the importance of separating control and data flow in your application. Dispatch queues are great to organize control flow in your app, but they weren't really designed as a general purpose data storage mechanism.
[Transcript missing]
The next topic is how to update state asynchronously very efficiently by using dispatch queues. Imagine in our app we have a progress bar that keeps track of how many thumbnails have already been rendered.
And we want to very, very cheaply update the state of this progress bar from anywhere in our app. We will use a dispatch source for this. Here we're creating it with the dispatch source type data add. and we ask for the callbacks from this queue to be called on the main queue.
Now we're setting up an event handler for this source that simply gets the pending data from the source object and updates our progress view with the progress that has been made since the last time our block was called. And once we've resumed this source, we can, from anywhere in our app now, update the progress by calling the dispatch source Merge Data API. This will very cheaply just do an atomic add operation to merge in the state and trigger the execution of the event handler. Importantly, though, the dispatch source infrastructure takes care to coalesce potentially many of these update events into one single invocation of the handler.
So this is something you can use this to make sure that you're not overwhelming the main queue with many requests to update the progress bar. It will only execute as many times as the main queue can actually handle and maybe update the progress bar for a larger amount of progress for every invocation.
Another advantage is that you can cancel dispatch sources. So if the user has completely switched to a different part of your app where that progress bar isn't even visible, you can just cancel this source and prevent any invocations of this event handler to occur in the future, even if there have been some updates in the meantime with the dispatch source Merge Data call.
Our last topic is moving code out of process with XPC. We've already seen how you can use XPC to apply the principles of fault isolation and privilege separation to your code. And one way to think of this is to improve the user experience of your app by not just not block the main thread, but don't crash the main thread.
So moving code that may have to operate on untrusted data or that is known to be prone to crashes or security issues out of process is a good way to improve the user experience of your app. And in this infrastructure that we've described of one queue per subsystem, you can simply think of XPC connections as a remote queue.
And for many more details on the XPC, we've introduced an awesome new API in Mountain Lion in Cocoa for XPC that was covered yesterday. You should definitely go and see it. If you've missed it, go and see that session on iTunes once it becomes available, along with last year's Introducing XPC session.
So here we've represented the same architecture of our app as before, except we've decided we should really put the image rendering subsystem of our app into a different process. It's operating on network data, image data that comes in from the network, and we can't be sure that that won't cause us to crash or to have a security issue.
And except for doing that, for moving that code into a separate process, our app architecture is exactly the same. We still send messages back and forth between the subsystems, except that some of these messages now travel over XPC. But if you've already applied the one-cube-a-subsystem idea and subdivided your app into different areas, it is actually very, very easy to apply XPC to this and extract some of these subsystems into their own processes that may have different privileges or may need to not cause the main app to crash.
So in summary, let's review the design patterns that we've seen. Number one, most important to remember, don't block the main thread. You do that by running code in the background with GCD and blocks. And while doing that, make sure that you don't block too many background threads with blocking IO operations or other blocking operations. We've seen how to integrate with the main run loop when you write GCD code.
How to apply the one queue per subsystem pattern and read a writer access pattern when you have subsystems in your app that support reader access. How to separate control and data flow and the importance of doing that. And how to update data synchronously with dispatch sources very efficiently. And finally, how to move operations out of process with XPC.
For more information, we have some excellent documentation on the developer site. The Concurrency Programming Guide covers GCD and related technologies. The Daemons and Services Programming Guide covers XPC. And the Transitioning to ARC Release Note goes into much more detail about the blocks and ARC topics that I've covered earlier.
If you have any questions, please contact our evangelist, Michael Turowicz, or ask on the developer forums. There is dedicated sections for both GCD and XPC on those. We've already gone over the related sessions. Don't forget to go to the Adopting Automatic Reference Counting repeat later. And that is it. Thanks a lot.