Building Concurrent User Interfaces on iOS

Session 211 WWDC 2012

For a great user experience, it's essential to keep your application responsive while it renders complex UI elements and processes data. Learn how to use concurrency at the UIKit layer to perform drawing and other common operations without blocking user interaction.

Speaker 1: Ladies and gentlemen, good morning. Today we're going to talk about building concurrent user interfaces on iOS and you've read the title but let me tell you what I mean by concurrent user interfaces. In particular, what I'm going to teach you today is how to keep your applications responsive, reactive to user input, even while they're doing a lot of work. Now, if you've never done any concurrent programming before this is a deep and complicated field. It's full of trap doors but I am hoping that I can give you enough of the fundamentals today to get you on your way and enough understanding to avoid some of the pitfalls that are here. If you've done concurrent programming before on the other hand, you may feel like well I must still be in bed. I must still be dreaming because I thought that you can't do any concurrent anything with UIKit it doesn't support it. Somebody on the internet told me so but somebody on the internet was wrong. Today, I'm going to teach you a number of techniques where you can leverage that existing concurrent programing knowledge that you have and some best practices for how to do that in particular with UIKit. So all of that out of the way, let's dive in. Say that you've got an app that downloads a bunch of JSON from the internet like about half of them do but maybe it's a particularly large blob of JSON so you need to parse it. Then maybe you're going to massage that data in order to get exactly what you need for your application. That could take awhile and depending on how your application is designed you don't necessarily need to block your user from interacting with your application during that time. So I'm going to teach you how to process that data concurrently with respect to handling touch events, handling accelerometer events, all of that so your application will feel really fluid. Next up once you've got that data processed. You may need to make some very fancy rendering of it. You're going to draw a pretty plot or something like that and maybe your drawRECT takes a 100 milliseconds well again, depending on the design of your application that doesn't necessarily need to block your user from doing things with it. You don't want it to feel laggy just because you're making some complex drawing. You don't want to punish your user for that. I'm going to show you how to move drawing to the background as well. Then once you've got all of these concurrent operations happening, your juggling all these things at once, you don't want more balls in the air than you have to have. So if the user's done something like navigated away from a part of your application which means that one of the things you started up is no longer relevant. I'm going to show you how to cancel it early so you don't perform unneeded work. Wasting your user's battery life and slowing him down from getting to what he actually wants to do. This is the app we're going to be looking at today. It's part of my get rich quick scheme. It does some fancy analysis of the stock charts and tells me where to put my dollars. It's basically a big gambling helper and I like to show you what we're going to start with and we're going to be working on this app all morning. So I've got xcode here. Let's build and run and see where we are. So as I click in here, nothing is responding at all and this is even worse but by the end of the day we're going to have the situation much better. You'll notice even if I had changed my mind and clicked on the wrong category. I can't even back out until all that data loads. That's pretty nasty. So we're going to spend the day fixing this application up and whenever you have performance issues the first thing you should do is get data because you don't want to fix potential performance problems without actually knowing what's wrong. To get data about your performance we like to use instruments. So, I'm just going to go back to xcode and choose product profile to run this application in instruments. When instruments ask me what kind of instrumentation am I doing, I'm going to choose the time profiler because what I care about is where is my time going. Why is it taking so long? Now the application runs again and as I click into this category here. We see this big purple plot indicating that the computer is thinking really hard. We go back here and see another big purple plot. So we're wondering about these big purple plots. What's hidden there? If I hold down the option key and drag over one of them I can filter to just that. Down here in this code tree area I can see what's taking so much time quickly by holding down the option key and clicking on this disclosure triangle. As I scroll down I see okay it's all of that fancy stock smoothing that's supposed to be predicting the future for me. That's what's slowing me down here and it's all happening in response to some network data being received see our request connection did finish loading. This is the section that we're really going to want to try to speed up. If we go back to the slides for a second I will give you an overview of how we're going to do that. Let's start with some nice schematics before we get to code. The model that we like to use is one of queues. There a little bit like lines at the grocery store. You got a bunch of people in that line. We're going to deal with the one in front and when he's done we're going to deal with the one behind him. Somebody comes in at the end of the line it's not his turn for awhile. So processing computational work on our system we use much the same metaphor. By default there's this thing called the main queue and all of the system events come in on that queue. So that's touches, that's accelerometer events because all of the work that you're doing in general is happening in response to these system events. You are doing things when the user touches that means that your work, i.e., this expensive work that we just saw is also happening on the main queue. The user touches and then we queue off some network requests. We say hey, give us some data about these stocks. That network data comes in also on the main queue but now we have some work to do because we got a bunch of data and we need to process it. All of that processing work appears on the main queue as well. Now this queue is pretty backed up. This line is getting long and if a touch event comes in now at the end of the queue and these processing events take a few seconds for us to get though them and that touch event will end up getting processed 15 seconds after the user actually touched. There's something very terrible. Intuitively, we would like that touch event to be able to jump to the head of the line. Touches are important so the way that we're going to do that is effectively by opening another line. We can have a data processing queue in addition to our main queue. What that'll mean is that when that network data comes in rather than in queuing a bunch of work to be performed, smoothing that data on the main queue. We're going to put that data on the data processing queue. So now when a touch comes in on the main queue if we have a dual core device we can even work on the touch event and the data processing simultaneously but even if we have a single core device the system will slice things up so that it gives time to both. It'll actually give more to the main queue because like I said a moment ago, touches are important. This way you can handle touches. You can handle accelerometer events. You can handle even incoming network data and [timers 00:08:21] while you're doing this extra work, this smoothing work. There's this one last little square here at the bottom. This one is new it's a box within a box. It wasn't in the last schematic and it signifies a new idiom that you need to be aware of when you're doing concurrent programming. It has to do with the age old adage that also applies here namely don't cross the streams. Consider what might happen if like I said a touch event comes in on the main queue and UIKit is trying to figure out which table view cell did that user just touch while you are modifying the data source which backs that table view cell potentially even on another processor. Calamity will result. This is really one of the biggest problems in concurrency and it's a very deep and complicated problem but I'm going to show you a guideline today which if you follow it should take care of basically all of the cases that you care about when working with UIKit. It goes like this, the main queue is UIKit's queue, it's where UIKit does it's stuff. It handles touches. It does rendering for the most part. All of these things that I've been telling you about. So if you want to insure that you don't touch anything that UIKit might touch at the same time then all you have to do is when you finish processing that data you send it back over to the main queue and actually update your user interface on the main queue. If you have a lot of expensive work to do you can do all of your expensive work on whatever queue you like just don't touch what UIKit's going to touch. When you finish all that expensive work you perform that one little extra step of sending the results of that work back over to the main queue and updating your user interface there so that you don't end up fighting with UIKit. That's a very small amount of extra time compared to all of the time you saved by doing that extra processing on a separate queue compared to your actual work. Now that we've talked through schematics let's talk about code. On our platform this queue idea that I've been talking about is modeled by a class called NSOperationQueue and you can make one of them like this. So now you've got that little queue figure that I've been showing you. You can even give it a name which is useful if you have a lot of them flying around because it will show up in the debugger and you can query it. You give that queue work by a number of approaches and this is one of them. We'll start using this one first, addOperationWithBlock. You give a block to the queue and you give it some work. Now the queue has something to do and let me show you what it looks like to do that box within a box situation. Just like we had a box within a box, we now have a block within a block. You'll notice the way that I shipped work off to the main queue was by actually getting a reference to the main queue, NSOperationQueue mainQueue. Then we gave it work the same way that we gave our own queue work, addOperationWithBlock. So when we get around to actually playing though that last operation we end up seeing the nice same schematic affect that we had in the last slide. We're giving the main queue work. We're sending that data, the resulting data from our processing, over to the main queue. Now that we've talked through the principle's let's put it's practice. Try to fix up our application a little bit. So looking at this instruments profile we see where the heavy work is. We see that it's in smoothing these stock points and we see that it's happening in response to finishing receiving some network data. We got some network data and so now we're going to process it. In fact, even initializing a stock is expensive so we're going to initialize those stocks concurrently which means that we need to go to see CRStocksTableViewController, request didFinishWithObject to do our work. This is the CRStocksTableViewController and if we scroll down a little bit we can find the relevant method, request didFinishWithObject. All it does is pull all of the objects out of the received data and create a stock for each one of them. Save it in an instance variable and then ask the table view to reload. We're going to move this work to the background. In order to do that I first need to create one of those operation queues I was talking about. So I'll add the instance variable to my list of instance variables and initialize it in this table view controller's designated initializer. Once I've done that I can start moving this work to the background. Here I'm going to add a block on to my queue and now all of this work will happen not in the main queue but on the queue I just created. We're not done yet though because you have to remember that box within box situation that I was telling you about. You do the work in the background and once you've got the result you send the result over to the main queue and update your user interface with the result on the main queue. Just like in the schematic we can do that by saying NSOperationQueue mainQueue addOperationWithBlock. Now, this is an operation which initializes all of my stocks and which once all of the stocks are initialized ships them back over to the main queue to update my user interface. You maybe be wondering, okay I understand why tableView reloadData needs to be on the main queue, that's touching the UI but what about this stocks instance variable? Why does this need to be updated on the main queue as well? Well if we scroll down a little bit we see our implementation of UI table view's data source method, number of rows and sections. We see that it returns the number of elements in the stock's array and so if UIKit is asking how many rows are in this table view while we are changing around the number of rows that are in that table view. Very bad things will happen. Consequently, we update not only the user interface on the main queue, using that shipping over technique but also anything which the user interface might be touching. Now, that we've done this let's take a look at what our project looks like. If I click in the mid-cap you'll see that it's letting me scroll at least a little bit until it tries to start rendering. The other nice thing we bought for ourselves is that I can change my mind even though it's doing all this expensive data processing. We still see that that scrolling performance was no good. So let's use instruments again to figure out why. Let me call your attention to a couple things. Here you see a line for main thread, main thread is basically like main queue and as I click here into this category we see the thing that is incurring all of this running time. The thing that's doing all of this work is not the main thread, i.e., main queue but rather this dispatch worker thread, i.e., worker queue. So that's great that means we moved that work off the main queue but now as I start scrolling around it's the main queue that starts incurring time again. Let's figure out what's going on. Again I'll filter to just this region and I'll see that CRStockCell drawRect is the culprit. So we've got our data processing out of the way and now it's our drawing that we have to worry about. Next I'm going to show you how to move your drawing to the background just like we moved that data processing to the background. Before we do that though, I would like to review what we just covered. Everything on the system that comes in touch events, accelerometer events, network data by default. All of these system events they're all delivered on the main queue and because you're doing all of your work in response to those events you're basically just implementing a whole series of callbacks to system events. All of your work happens on the main queue as well which means that when you're doing a lot of work you are blocking all of those things that arrive on the main queue. You can fix this situation by doing that expensive work on a non-main queue, one of your own creation, NSOperationQueue is the one way that you can do that. Then we covered this one additional tricky subject which is that if you were touching anything which UIKit might touch we don't want a kind of siblings fighting over the toy situation here. So when you're done doing that expensive work, ship the results back over to the main queue and update it there. Excellent. Let's talk about drawing, schematically. This is our situation. A touch comes in which causes a scroll to happen and now we've got a new table view cell on screen. We got this drawRect and you can see I've drawn it large to indicate just how expensive this drawRect is. So as we're sitting around waiting for this drawRect touch events are in queuing behind it just as they started in queuing behind the data processing work that we were doing before. It's not until we finish the drawRect operation that we handle those touch events. So just like with the data processing, what we're going to do is try to put the rendering on a queue of its own rather than putting it on the main queue blocking all of that incoming work. Now when a touch event comes down we'll put the rendering on a background queue and just as before we've got that situation where we can process touches that are occurring on the main queue even while the rendering is happening. Just as before we have the box within the box which sends the result of that rendering operation over to the main queue to actually do the work of updating our user interface. In order to show you the code, teaching you how to make this happen for yourselves. I first need to make sure that we're all on the same page as far as how UIKit actually gets pixels on to the screen. That has to not be magic first because right now it maybe magic for some of you. You have UIViews, you're familiar with this class. There is this method UIView setNeedsDisplay. That is the method that you call to say, UIKit I would like to draw something. I've implemented drawRect and I want you to call it. Behind the scenes what UIKit is doing is turning around and calling CALayer setNeedsDisplay because every view is backed by a CALayer and then sometime later when it's convenient for the rending system that layer will be asked to display. When it's asked to display it will create a region of memory called a backing store. It's basically just a bitmap that stores the results of all of those rendering operations. It's an image that's going to be painted on screen repeatedly until you call setNeedsDisplay again thus invalidating it. So it's that backing store we're concerned with. In your drawRect implementation you're going to use core graphics to draw into that backing store. We tell core graphics that this is the place we want to put the result of those drawing operations by creating a CGContext and pointing that CGContext at the backing store. Then all of your lines and shapes and things will go into that bitmap. Now you've never accessed this directly probably. UIKit surfaces that context to you via UIGraphicsGetCurrentContext which you've probably used if you've ever done any CG drawing calls of your own in drawRect. Even if you haven't the methods that you may be more familiar with like UImage drawAtPoint, the Bezier path methods, the UIGraphics methods. They all use this function too so we're all pointing at this backing store. This is where we're going to put the results of our drawing operations. Now our strategy is going to be instead of doing our drawing into this region of memory that core animation is managing for us we're going to replace our UIView with a UIImageView, make our own image, and draw that image on our background too. We're going to take this into our own hands. We're not going to let core animation make a backing store for us. We're going to make the equivalent ourselves. The nice thing is you can actually use basically all of the same code. This is a typical albeit useless drawRect implementation and we're going to keep it essentially the same as we create a drawRect implementation which doesn't just draw into the existing context that core animation has set up for us but which rather creates one of its own and draws into that and then pulls an image out. Instead of drawRect we'll make some function which returns a UIImage. Once again we need to take a size to know how big that image is going to be. We make the UIImage using UIGraphicsBeginImageContextWithOptions. The first argument is the size, how big of a region of memory do we need. This is specified in pixels except there's a couple other options that can affect how that works. The second one is whether or not what we're going to draw is opaque. If what we're drawing is opaque then we don't need to bother making an alpha channel. We can save a bunch of memory that way. The third option affects a pixels to points conversion. It's the scale factor so I said the first argument was in pixels. That's only sort of correct. The third option you might pass two if you have a retina display and that would mean that it creates an image that's twice as large as that first size argument but here I passed zero because that indicates just use whatever scale factor the main screen has. That's usually a sane thing to do. Once we've done our drawing operations we can get the image back out of this context using UIGraphicsGetImageFromCurrentContext and the image context thus cleaning up after ourselves and give that image back to the caller. That's exactly what we're going to do now. I'm going to take this application and make the drawings of those very fancy plots happen in the background. Here is CRStockCell drawRect as you can see it does many things. It's creating that very beautiful drawing of a graph for you but what I've created is CRStockRenderer which as you will be able to see in a moment has the exact same implementation. Let me line them up here for you except instead of as I said drawing into a context which core animation creates and manages, it's going to draw into a context which we control which we manage. It's going to return that UI image just like I showed you on a slide a few moments ago. So all this code is the same. I migrated it wholesale. What's different is these first few lines. Here we're saying if we've already drawn this graph let's not bother doing it again. Let's return that value over again thus caching the result, easy enough. The second line is exactly that beginImageContextWithOptions call that I showed you in the slides. We're saying we're going to make one that's desired size big. We're going to make it at whatever the main screen scale. I'm not thinking too hard about opacity. No, is always safe. Then all of this is the same. We scroll down to the bottom and as you can see it's matching up nicely. So all of this is lined up and we have just three extra lines at the bottom. First we pull the image out of the context and save it in an instance variable so that we can use it later as a cache. Then we end the image context cleaning up after ourselves and finally we return that result. Now we need to make the code which creates our cells use this renderer instead of using the stock cells drawRect implementation. Now you'll notice that CRStockCell it doesn't do anything else so we're just going to get rid of these two files wholesale because they're useless to us now. So if we go back to the table view controller. First off, I'm going to remove the pound import for CRStockCell, it's no longer relevant and scroll down to tableView cellForRowAtIndexPath. Here where we were going to make a CRStockCell. I'm instead just going to make a UITableViewCell. I'm going to set that image from our renderer as the image of the image view of our table view cell. We don't need to give ourselves stock anymore. We're going to give the renderer the stock instead. In order to take advantage of the caching behavior I was talking about which will allow us to reuse the same renderer's result over and over again. We're going to have to keep track of which renderer is which stocks. So scrolling up again to the top of the file where I have my instance variables. I'm going to add an NSMutableDictionary that maps names of stocks to renderers and I'm going to go ahead and initialize it here in initWithGroup. Now, returning to my cellForRowAtIndexPath method I'm going to start using it. First, I'll try to pull a renderer out of that map. Say do we already have a renderer for this stock name? If we do and it's already finished rendering then we're done. We just give ourselves image view that rendered graph and it's just like before but if the renderer hasn't already rendered we have a little more work to do. First, there's the question of have we already made a renderer? There's two possible states. We could have made a renderer which hasn't finished yet or we could have not already made a renderer for this stock. First, we're going to consider the case where we haven't already made a renderer for this stock. This stock is new so we're just going to allocate one of these CRStock renderers and give it our stock. We'll store that renderer in this map and then we're going to ask it to give us that rendered graph but we'll ask it to do that on our own NSOperation queue not the main queue. So as we're executing code here we're just going to breeze right by these lines and keep on going because this work, the expensive work, is going to happen on our own queue. Just like before once we've got that rendered image we don't update the cells image view on that background queue. We update the cells image view on the main queue so as not to fight with UIKit. We say, cell imageView setImage renderedImage, and we do that on the main queue. Now regardless of whether we already have a renderer starting for this particular stock or not we can set a placeholder image on our cells image view while that stock renderer is happening. What exactly make sense for your application will differ by purpose. Here I've just done a much simpler faster implementation of the same rendering path that can complete very quickly so that it'll give us smooth scrolling while that rendering is happening in the background. Here I'm getting the placeholder image of a particular size and putting that on our cells image view while on our background queue we're performing the more complex rendering. So now if I run, we see that I can scroll smoothly while stuff is rendering and we're at 60 frames a second which is fantastic. There's just one other thing that I want to show you which is that now if we go to profile this application and I click in the mid-cap and immediately click out this purple plot continues. It's still doing all that work because we started up this work on our background operation queue but we didn't tell it to stop. We didn't tell it hey, the users not looking at this anymore. Don't waste your time, don't waste power. So we're going to look at how to do that next. Before we do I would like to review the concepts about rendering that we just covered. Like everything else by default the rendering happens on the main queue. Your drawRect implementation is called on the main queue. So if you're drawRect implementation is slow you're slowing down the main queue and all of the events that are processed on it touch events, accelerometer events, etcetera. So just like with the data processing rendering can be moved out to your own queue as well. You just draw an image there and when you're done you ship it back over. You can use any of the UI graphics functions. You can use UI Image methods. You can use UI Bezier path methods. You do have to make sure that if you say UI graphics begin image context like i was showing you. You call UI graphics and image context in the same operation otherwise the results are undefined. That's one thing to keep in mind. Finally as before UIImageView setImage is no more safe to call from a background queue than any of the other things we've been talking about where you could potentially fight with UIKit. So even though what you've created here on your background queue is an image not some data, not a string. When you're done with it you ship back over to the main queue and you say, hey main queue. I want you to be the one to update my image view for me. Now let's talk about cancellation. Not keeping more balls in the air than we have to. If we have a situation like this and the user navigates away from the set of stocks that we're looking at we would like to not do any of this work. There is a method that we can call that will do something like that called NSOperationQueue cancelAllOperations and that will blow away not all of the operations on a queue but all of the ones that haven't started yet. That first one is still there and the reason for that is that we can't possibly know if at any given point during your operation it's safe to just cut it off because most of the time it isn't. That operation keeps running until it completes which might be fine for a small operation but if it was more convenient for you to set up your operations like this where you've got one really big one then now you're in trouble because you say cancelAllOperations. What you ended up canceling was the useless, tiny, incredibly fast operation not the one you actually care about. So we have a solution for that too. We can say in our operation when it's safe to cancel me. There's a piece of API that you're going to use called NSOperation isCancelled but first I need to introduce what NSOperation is because right now we've just been talking about NSOperation queues. So far we've been doing work that looks like this. We've been saying queue addOperationWithBlock but that's actually just a convenience method. It's a short hand for this long hand. You can actually explicitly create an NSBlockOperation which is a kind of operation and give it some work to do and then put that block operation on your queue. Now if we have something like this where we're going to loop over and over again a bunch of pieces of data and then process each of them because maybe it was convenient to set things up this way. Then in the middle of our loop whenever we know it's safe to break out we can see hey, have we been canceled. If we have we can breakout potentially doing some cleanup if we need to. We can close any transactions that might need to be closed. We can make everything happy and avoid unneeded work. There's just one catch here which is that we've just created a reference cycle. I want to review what this means for those of you who aren't familiar because this issue will come up a lot when using the NSOperation APIs. So our queue because our operation is on the queue has a strong reference to that operation and the operation of course has a strong reference to the block which contains the work for that operation but in that block we've referenced the operation. In Objective C when you create a block which references an object the block gains a strong reference to the referenced object which means the operation reference is the block and the block references the operation. This creates an object cycle that will never be cleaned up without external intervention. The easiest way to deal with it is just to create a weak reference to the operation instead and to use that inside of your operations block rather than the strong reference which would create that cycle. Now you see we've got a dotted line from the block to the operation so we don't have that reference cycle anymore. We've talked to the theory, let's put it into practice. Here we've got an implementation of viewWillAppear. Intuitively, what we want to have happen is cancel everything. Stop doing all of that wasted work when the view disappears, when the user navigates back to the list of stock groups. So we can just do that by implementing viewDidDisappear. It's a one line implementation. After calling super we just say hey, operation queue, stop doing all that. Unfortunately, we're not quite done because for the sake of expediency and for the sake of having something to demo to you this operation is extremely long running. The one which we created in the very first demo. In every step of this operation we would like to be able to bail if this operation has been canceled. If the user has navigated away from that stock listing we'd like to be able to say hey, stop smoothing the data for the stock listing. It doesn't matter anymore. So we know it's safe to cancel operations here. In order to do that I'm going to make an explicit NSOperation rather than this implicit one that I've made here so I'll just alloc init NSBlockOperation. I will put this same execution block unchanged into this block operation. Then I will put the block operation on to my operation queue. It's just a long hand version. It'll give us a little more power. Now, from within the operation I can say if the operation's canceled I know it's safe to return here. You might have to do some fancy cleanup. You might have to do some special work in your own code. Here we're just moving stocks and we're going to throw them all out. So it's okay, we can return here. [Clang 00:37:21] is warning us of the same thing I warned you. Capturing operations strongly in this block is likely to return lead to a retaining cycle. Right, you are [ Clang 00:37:32]. To get around that we create a weak operation reference and refer to that instead inside of our operation block. Now [ Clang 00:37:44] is no longer upset with us. That's always good. If we now run our application. I should run it in the profiler so I can show you. Excellent, so here I'm going to click in the mid-cap and then I'm going to back right out immediately. You see that it cuts off much more quickly than it did last time. So it's just a little spike. It doesn't go on and on wasting our user's battery, wasting our user's time. That's great but there's one more issue which is if I scroll this list really fast you see these jump. They appear and then they change. That's a fun bug. It also has to do with cancellation. If we scroll down here to our implementation of table view, cellForRowAtIndexPath we see that the work that's happening in the background looks like this. We're going to ask our renderer to give us an image representing the plot for that stock. Then once we've got it we're going to ship that image over to the main queue and set it on the cell which we were configuring when this method was first called. The issue here is that this method may have been called awhile ago and this cell thing that I have selected may not be the cell that I think it is. In particular, if table view cells scroll off screen then they are reused for new on-screen cells. So we may actually be setting the plot for a stock which we don't need to be setting it for. That is unfortunate. We're going to fix this in two ways to be thorough and I'm going to explain why specifically I'm fixing it in two ways. Way number one is a useful one to know about and it refers to a new piece of API in iOS 6 that I want to show you. That is that when a table view cell goes off screen we want to cancel these operations. We don't need for our system to be wasting time computing plots for a stock that's off screen. So we're going to cancel those operations but we can't say NSOperationQueue cancelAllOperations because we don't want to cancel them all. We want to cancel the one for the cell which just went off screen. We want the ones for the cells that are on-screen to continue please. In order to do that I need to keep track of each stock's operation separately. I'm going to scroll back up here to my list of instance variables and make another mutable dictionary. At this point you got two mutable dictionaries mapping stock names to something. If this were a real project you would probably create a real structure for this but this is a demo. We have two mutable dictionaries each of them map stock names to a thing. This one maps stock names to rendering operations. I'm going to instantiate it in my designated initializer. Now, if I scroll back down here instead of saying hey, queue addOperationWithBlock I'm going to say NSBlockOperation blockOperationWithBlock thus making an NSBlockOperation I can refer to. I'm going to put it on my queue. This is a little bit of a long hand form and then I'm going to store it in my dictionary so that I can get to it when my table view cell goes off screen and cancel it. Before I do that, there's one other tricky situation here which is that even if I cancel my operation this code may end up executing anyway. Like I said canceling an operation doesn't guarantee that that operation won't run because if it's already running we can't stop it. Furthermore, simply saying if not operation isCancelled here is not safe enough either because it could be canceled right after we check that. We're going to do something slightly safer within our operation as well. Rather than saying, cell imageView where this cell could be referring to a table view cell that is now been re-purposed for some other use. I'm going to say, tableView cellForRowAtIndexPath with the indexPath that was originally passed in. So if that cell is still the same one that I think it is it's going to return the cell I was initially pointing to. If my cell is now off screen it's going to return nil which is lovely for this purpose because when I try to get it's image view that will return nil. When I try to set image on a nil variable nothing will happen. Now that we've done all this. We can cancel our table view cell's operation using a new piece of NS table view delegate API called tableView didEndDisplayingCell forRowAtIndexPath. Whenever a table view cell scrolls off screen this method will be called and this is your opportunity to do any kind of cleanup that you may for operations which you may have started late into that cell. If you've allocated a bunch of side table resources for that cell this would be a good place to tear them down. In this example, if you have started up some long running concurrent operation associated with this cell this would be a good place to cancel it. So here's the code to do that. First, we fetch the name of the stock in question and then we pull the operation for that stock out of that dictionary I created. We have a reference to an NSOperation. This is the task that is running or hopefully hasn't run yet because if it hasn't run yet we can still stop it. If there is such an operation we're going to ask it to cancel and then we're going to remove that stock from this map. That's it. So if we run our application and now scroll really fast once everything is here we'll see that it doesn't pop around anymore. Everything is happy in the land of our application and that means that I'm going to get rich quick as soon as I get off this stage. So we just learned some things about cancellation. Namely, that there is a method called NSOperationQueue cancelAllOperations. You can call that to throw out any operations that are running or that have not yet run on a particular operation queue. You can also cancel a specific NSOperation by sending that operation a cancel method but remember if it's already started that's not good enough. That's not going to cut it off. So you can check within your operations to see, has this operation been canceled already. There's that isCancelled method that I showed you. You want to check that periodically when you know it's safe. Before I go on and wrap up, give you some more parting words of wisdom. For those of you leaving I do actually have some more new things to say. I want to point you to some excellent resources. There's a gentleman named, Jake Behrens, who wears plaid and who can answer your questions. He's great. There's this concurrency programming guide that you might want to check out. It's got even more details about some of the pitfalls that you can really run into once you start leveraging this stuff heavily. Finally, there's the Apple Developer Forums which is the place where you can help each other. There was one particular piece of parting wisdom that I wanted to give you which is that I've taught you how to make these own queues, these queues of your own, to get out of the way of the main queue. That's great but once your application gets sufficiently complicated you might want to get out of the way, not of your main queue, but of your own queues. So you can make more of these. You can make queues and queues and queues and they can point to each other. They can have dependencies on each other. This whole thing can get very gnarly very fast. In UIKit land, we've dealt with some nasty consequences of that. A parting word of wisdom that I wanted you all to take with you had to do with that scheme that we had for shipping data back over to the main queue. That is a useful philosophy and it can be applied to your own sets of queues as well. Typically called the share nothing philosophy and so if you've got your own sets of queues which are competing over a piece of data. Like they're both going to be modifying it. They're both going to be reading it and writing it. Then you might want to use that same strategy that we used for UIKit's queue versus your queue. Say, hey this one queue I've made, this is the canonical owner of this piece of data. So on this other queue I've made and maybe this third queue I've made, when I want to modify that piece of data, when I've got some new value for it. I'm not going to just modify it on that second or third queue because that's just as much crossing the streams as it was for you to update your image view on a secondary queue and then fighting with UIKit. You can say on your second and third queue hey, first queue I want you to do the work of updating this data. Ship the data over to the first queue and have it do that. I hope that this talk is going to help you create tremendously smooth, fast, responsive applications and that you all have a wonderful remainder of your week at WWDC.

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US