Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: tech-talks-2009-4
$eventId
ID of event: tech-talks
$eventContentId
ID of session without event part: 2009-4
$eventShortId
Shortened ID of event: tech-talks
$year
Year of session: 2009
$extension
Extension of original filename: m4v
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

iPhone Tech Talk World Tour #4

Effective iPhone App Development, Part 2

2009 • 1:03:28

Whether your iPhone app is currently in development or already on the App Store, strong code architecture is essential. Learn the most effective techniques for data modeling, communication between view controllers, and when to use delegates and notifications. Find out how to make important decisions about memory usage, performance, and a responsive UI. Developers of all skill levels can benefit from this thorough examination of iPhone SDK best practices.

Speaker: Lawrence Coopet

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Hi, I'm Larry Coope, Apple engineer. This video builds on Part 1 and focuses on leveraging system technologies to make your application faster and more responsive. Today we're going to talk about taking the best advantage of the OS and the frameworks that are built into the iPhone OS. I want to contrast Part 2 versus what we talked about in Part 1.

In Part 1, we talked about code flow and data in a sort of abstract sense. In Part 2 today, we're going to talk about the implementation realities of building an application and extending its functionality day to day. So implementation realities. And how are they relevant to an effective architecture? Well, we want to talk about specific areas like UI table view, UI image, adding concurrency with NS operation, and working with system view hierarchies. And we're going to hopefully see lots of code along the way.

If we talk about the title of this presentation, Effective iPhone Application Architecture, what does it mean to have an effective architecture? What makes it effective? And for me as an engineer, I think the number one thing is it's flexible. It's easy to update and add features to. Good architecture leverages the OS, and that helps it be robust and efficient.

And that ability to leverage the OS to the fullest is very important because it lets you, as a developer, be lazy. Take advantage of the hundreds or thousands of man-years of code that's been built by Apple that's there for you to take advantage of and do your bidding inside your application.

And to do that, you really have to have a knowledge of the available technologies. And this means going deeper every day. As you extend your app's functionality or you build another app, you need to make sure that you spend time understanding these APIs and get the proper usage of those APIs so that you can benefit and not have to reinvent the wheel every time you write your application. These things are there for you to use.

Historically, I've seen developers spend days or even weeks taking time to write code, and I sit down with them, and I say, by the way, do you know that there's a single function that does that? And they can get very upset. And we're going to talk about that today, about understanding the OS and making sure that you don't waste time reworking system plumbing and taking advantage of those frameworks to get the best possible benefit. So let's get right to it. I want to talk about specific examples and how they affect your application no matter what kind of app you're building.

So let's talk about UI table view. Nearly every app has at least one of these, and they convey a lot of information to the user. But we see some symptoms and we see some problems with other applications in the App Store. And the number one being that their UI table views have some scrolling or performance issues. So before we can talk about that, I want to review the UI table view structure.

So as you can see, there's quite a bit going on inside the UI Table View cell structure. And of course, it already starts pretty complex. But then when I go into accessory mode or edit mode, I start to see even more views built into that system. And then when we select something, in fact, we insert another background view in there to show that highlighted state.

And then, of course, you as the user of the cell in your client app, you may want to show quite a bit of information in there. So why does this matter? And how is this related to an effective, performant, efficient architecture? Well, it matters because composited drawing performance.

It's important to remember that UIKit is fully and pervasively composited. And I want to emphasize that this is not an opt-in or an opt-out strategy. It is a composited system. And this significantly impacts how you write your code because you might be in unfamiliar territory. If you're coming from other platforms, this may not be something that you're used to. And it can have a dramatic ripple effect throughout the system because there is a lot of information in there. So what we want to do is that potential extra step for compositing your view into the hierarchy. So let's go back to the UITableView cell as our example.

And you can see what can happen is in a complex system, a complex view, not only is the UI complicated, but I've actually dramatically decreased my performance if I'm not careful how these things are built. So we see in this example that there's a potential extra composite per view in that little table view cell. So this, as I talked about, has a ripple effect because composited drawing performance is important everywhere.

Even though we're talking about UI table view cell, I want to emphasize we're talking about general issues. And that composite drawing performance can affect other parts of your application. I see developers who have serious performance problems in their UI table view cells, but they don't understand that they could get a lot more performance even in their more static UIs by understanding compositing.

So UI complexity now not only impacts your user's experience, but it impacts performance as well. And it's important to remember that opacity does matter. The system can optimize fully opaque views through a separate drawing path. If you're not fully opaque, then the system will have to composite you with your views and may cause some performance degradation.

So how do we get around this? What do we recommend? How do we increase drawing performance everywhere in the system and not just in UI table view cells? Well, obviously, simplify. We talk about in Part 1 about staying focused and drill down on your UI and data. So it's important to do that here as a performance benefit as well as a usability benefit.

And you can flatten your drawing with custom views. This is something developers forget about. If I have a relatively flat view hierarchy, and in our example, for my table view cell, I've got my little image and I've got a label underneath it. If I know that that is always going to be presented that way, I can put the, instead of having a UI image view and a UI label, I can put the UI image and the NSString in a single custom view and always draw them in that order, and I will encapsulate not only my UI items, but I have one less view to composite. And so that will definitely help me with performance.

So besides simplifying to increase drawing performance, it may sound trite, but minimize drawing. It's important, if you can, to use things like set needs display and rect versus set needs display. For complicated view hierarchies, I can use set needs display and rect to tell the system, oh, I'm just updating this corner. I'm only updating this widget. So use that judiciously where you can.

And of course, you need to respect the rect and draw rect where it makes sense for your application. If you know that you can draw parts of your view, then absolutely, you should use set needs display and rect and draw rect. Make sure that you look at that draw rect because that is going to be the dirty rect of your region that needs to be drawn.

And also, as we're talking about today, this is all about understanding the OS drawing and updating policies. When I see developers setting view.opaque is no, and then I ask them why are they doing that, and they're not sure, that's a problem. View.opaque is yes by default for a reason.

That makes sure that the system can take that optimized drawing path through the code where it makes sense. And as I talked about in Part 1, never call draw rec directly. It's important that this, in this system, which is a true Windows Server-based system, that you understand that you should never call draw rec. You should call set needs display or set needs display in rec.

That simply sets a bit. As I said earlier, if you call draw rec directly, not only will you probably not draw properly because the context isn't set up, but you're actually taking a performance hit because by setting needs display or set needs display in rec, you're simply telling the system that this, this object needs to be invalidated and drawn at some later time. So never call draw rec directly.

One of the other symptoms we see in a UI table view cell is highlighting problems. And again, let's review highlighting and selection briefly before we go on. When we select something in a UI table view cell, there's several things that happen. First, we save off the highlighted, opaque, and background color properties of all the views in that system. And this is interesting because highlighted and the set highlighted methods are not required by all UI views.

But if you support it, we check for that using response to selector, and we save off that state. Then we insert that background view. Then we set all the views opaque property and background color and highlight it. Opaque is set to no, background color is set to clear color, and the highlighted state is now set to yes, if your view supports that highlighted method. Now, when we deselect, we start the fade of that selected background view that we inserted earlier. At the halfway point, we reset the highlighted state, And at the end of the animation, we reset the opaque and background color properties.

So now that we've reviewed that, why does this matter? It's simply because these highlighting problems come about because people don't understand API compliance. Your derived objects should be aware of what they're responsible for implementing. And in this particular case of UITableViewCell, you're responsible if you want to respond to those states to implement set highlighted and the highlighted methods on your objects in your view hierarchy. And it's also important to remember that we're in an object-oriented system. So you need to trust UIKit to do the right thing. And for UI table view cells, I see developers polling for the highlighted state, but there's no need to do that.

Especially as you saw in our example, when I'm deselecting the view, it's actually an animation state. So polling can be highly problematic. So you need to understand and trust UIKit to do the right thing for you and tell you what your state is, and then when you are asked to draw, you draw on that state. So API compliance is critical to get the best possible experience for your users and your code.

So we've talked about UI TableViewCell as an implementation reality in building our architecture. Now I want to talk about UIImage. And unlike UITableViews, every application uses these. Lots of applications use TableViews, but every application uses UIImage. So I want to look at two of the primary loading APIs inside UIImage.

Image Named and Image with Contents of File. And what's interesting here is we're looking at our table, is that there's some small differences and some very big differences between the two. In Image Named, it takes a simple name inside your bundle. Image with Contents of File takes a full path to a file that you want to load.

But here's where the big differences come in. Decompression. Image Named is an immediate load. That means when you call Image Named, it goes out to disk, loads that, decompresses, and that image is ready to use right now on the return from that function. Image with Contents of File, as we say, is lazily loaded.

The data is memory mapped in, but no decompression happens unless you actually try and put that image on screen. Also, Image Named is cached by the OS. Image with Contents of File is not. So every instance of Image with Contents of File gives me a fresh instance. If I call Image Named with myimage.png a thousand times, I'm going to always get that cached image. So there's a big performance win there by having it in the cache. Now under iPhone OS 3.0, these are both purgeable by the system, which means that not the object, but the backing store, the data behind that object, can be flushed and recovered in low memory situations.

And when memory is recovered, the system knows how to automatically go out and reload that image for me. So this is really powerful technology. So how is this relevant to our session today? Well, simply by choosing the appropriate API, I can get dramatic behavior changes in my application. So based on the application context, it's important to understand the behavior of these very frequently used APIs.

So let's talk about how we use image name properly, given the differences between image named and image with contents of file. Certainly, we want to use it for UI components that we're using frequently inside the system. Button backgrounds, in our table view cells, any edit accessory views, anything like that that you're going to use frequently. But also it's important to remember what not to do, and that means try to avoid too many of these in one spot.

Especially in inside application did finish launching or view did load, or can it dramatically affect the load times. Remember, these are blocking loads. Everything has to be prepared every time. So this can dramatically slow down your app or view loading if you're trying to load these in a loop or as a large set. So just be aware of that.

And it's important to point out that iPhone OS itself uses image name to load nib-referenced images. Just be aware of that. If you've got, you know, lots of complicated UI with lots of image backgrounds, you may want to break that up into separate nibs to speed up loading.

Using image with contents of file. Well, obviously, this, since it's not cached, this should be used for any images that will not be needed immediately. Obviously, you know, used infrequently or rarely. So the important point to point out here is if you're going to put up an image in a view that comes up infrequently, you absolutely want to use image with contents of file because I don't want to use image name in that case because I want to avoid putting image pressure on the image cache. Lawrence Coope I have the image cache dump out a frequently used image for one that's almost never used. So it's important to use image with contents of file to make sure you do not put unnecessary cache pressure on the system.

Obviously, since this image with contents of file takes a full path, this is used for all non-resource bundle images, anything outside your bundle. But remember that I can use it for bundle resources. It's pretty trivial. You can see in one line of code here how I can get a resource path, a full path, to any image inside my bundle so I can put my rarely used images inside my bundle and have direct access to them, but I can load them lazily anytime I want. So you are free to use image with contents file for your resource-based images as well as image named.

So let's not forget what UI image is wrapping. Under the hood, it's CG image. And CG image is part of the core graphics framework. And CG, core graphics in general, provides a very deep, wide set of functionality. Remember I said earlier that there's hundreds, if not thousands, of manures inside these frameworks, and we want you to take the best advantage of these. And this is a common iPhone OS theme. We provide these very powerful, lightweight wrappers that encapsulate this incredible technology under the hood.

And those wrappers provide trivial access to and from the underlying components. In this case, It's one line to get the CG image out of that UI image. Then it's available to you to drop down into the core graphics layer and do all sorts of interesting things with those images.

And inversely, it's also trivial, one line again, to go from the CG image to a UI image, which is expected everywhere inside UIKit. That's the component, that's the object that you need to pass around. So it's easy, given that you've manipulated your CG image, to go back and generate a UI image.

So looking back at our agenda, we've talked about UI image. Now we want to talk about my favorite subject, adding concurrency with NSOperation. But first, what, why, where, how? What is concurrency? Obviously, this is doing more than one thing at a time. We call it multi-threading or simply threading.

But I get a lot of questions about why do I need to even bother with this technology? It's important because we want to keep the UI as responsive as we can and to leverage idle system resources, especially the CPU. And this is important even on single core processor machines. You can definitely get a dramatic performance win, as you'll see in my demo later, even on a single core by using multithreading and NS operation.

But we often have to talk about where can I take advantage of concurrency and threading? Well, obviously, any intensive computation. You don't want to block the user experience in the UI because you need to go off and calculate some data set, or you need to parse some incoming XML, or you're going to do some image manipulations on the phone. You don't want to block those operations in a synchronous way. Also, any sort of significant disk I.O. can really benefit from threading.

And accessing large data sets, of course. And I want to point out non-uniform data. Non-uniform data like image data, where I don't know the compression rate, the size of the images, I don't have, you know, an order N computation here. I don't know how long each operation is going to take. So this is really where you can benefit from threading. Of course, importing data as well, which is another significant disk IO operation.

But when I talk about where to take advantage, it's important to point out that you instrument your code before you decide if threading is right for you. Concurrency isn't always a win. You must make sure that you give the system enough work to do. I don't want to instantiate a thread and incur all that overhead just to add A plus B together. So you need to make sure first that the granularity of your problem is correct, that you have enough work for that NS operation to do that task to make it beneficial. Thank you.

So just as important about talking where to take advantage, we need to talk about where to avoid concurrency. And the number one problem area is networking. Remember, these networking operations are very high latency. We don't want to use the full power of the CPU to spend time waiting for our data to come down from the network. And for those networking situations, we have built-in asynchronous APIs where the system will handle that concurrency for you as part of its run loop.

Lawrence Coopet And it's important when you're doing these networking operations to avoid blocking. You want to avoid these synchronous API calls that will leave your users waiting for their data to be downloaded. Lawrence Coopet Example, NSData has a knit with contents of URL. If you call that directly, the system is going to block while it's waiting for that data to download. The asynchronous APIs will let you get around that and avoid those blocking situations. reachability. It also has asynchronous APIs that you should use that prevent you from blocking the user's experience.

And one of my favorite areas, don't use concurrency and threading for idle behaviors or idle operations. We have much better constructs that can be used. Like NS-Timer. Don't use threading for idle behaviors. Remember, NS-Timer gives you this nice regular heartbeat into the system and it's very easy to use. And there's no reason to use threading in that particular situation.

So historically, the challenge has been with threading that threading has always been kind of hard. And as I say, I think it's really hard, especially when you talk about managing your state of your application and its data across multiple threads in a shared way. This becomes very difficult.

And the interesting thing is iPhone OS and NS Operation make it a lot easier. And you're going to see how in a minute. How do we simplify concurrency using NSOperation? The most important thing is keep object access confined to one thread. One object, one thread. This is absolutely the safest pattern for implementing concurrency in your app. Then you can forget about locking, signaling, sync points, mutexes, semaphores. All these things that are highly problematic for a traditional threading model go away. An NSOperation and NSOperationQ provide this one-to-one mapping, one object, one thread, this very safe pattern for concurrency.

So let's talk about NSOperation. And I feel it's compelling object-oriented encapsulation. NSOperation provides everything you need for implementing that task. It encapsulates your code and your data, your state. You can query for if it's executing, it's finished, has it been canceled. And it allows you to set up dependencies between operations.

I can tell the system in a single operation queue I want this operation to depend on this other operation. That means that this other operation must finish first before the previous operation will even begin to execute. very, very powerful stuff. And I can set priority in my queue. So I can put arbitrary operations on the queue and say, I want this one to have a high priority and this one to have a low priority. And the system handles all this for me.

And the thing I love about NSOperation and NSOperationQ is it's really easy to add to existing code. Sure, you have to refactor your existing functionality, but that's always a good thing. I always talk about factoring your code to make it more flexible and reusable. And by using NSOperation, it kind of pushes you in that direction of factoring your code. And again, the heavy lifting is all done for me, and you'll see in the demo how easy it is to use NSOperation.

Also, for NSOperation Queue and its encapsulation, it provides all the things that you would want in a queue-based system. I can suspend or resume the queue. And I can trivially change the number of concurrent active operations, so I can throttle up or throttle down how much load I want to put on the system. Very, very interesting.

So NSOperation and NSOperationQ. They're very easy, but yes, you still have to write code. You subclass NSOperation and override one method, main. Now typically, of course, you'll override, you'll have a designated initializer that you'll provide, and you'll typically override DLAC. But main is really the workhorse. That's all you have to do.

You create the operation and you give it the data it needs to do its work. Again, we're talking about transferring ownership of the data. Do not share data. You transfer the ownership of that. You pass the array it's going to work on. You pass that XML data into the operation. You do not share it between objects.

You create an NSOperationQueue and add your operations to it. You never, ever have to over or subclass NSOperationQueue. Only NSOperation. When the operation finishes, you simply use the result. So I have a slide here, which will show us concurrency illustrated. First, we're going to create, here's some code, we're going to create our NSOperationQueue. I have my alloc and init.

And in this case, I'm going to set the queue immediately to suspended. By default, queues are always running. But for this example, I want to show you that we can suspend the queue, and then we're going to start building our operations. And this is code pretty much right out of my demo code that's going to try and load and scale an image on the fly.

So my load operation is my subclassed operation object, that subclassed from NSOperation. I have my own designated initializer called init with path colon. I'm going to create that operation. I'm going to set myself as the delegate, and we'll see why that's important later. And then I'm going to add myself to the queue, and I'm going to release my operation because, of course, the queue, like all good Cocoa objects, is going to retain that object since it needs to hold onto it.

And you can see that the objects start to pile up in the queue, and then I'm going to set that queue to set suspended to no. So now I'm going to see, I see my animation go, and I see my objects, my operations moving through the queue, being allocated to a thread, and being executed. Now again, this is the code. I do not have to do any of that plumbing work myself. This is all handled for me by the system. Very fantastic.

So, the next step. You say, great, Larry, I've done all this work. I've created a subclass in this operation. How do I know when the operation's done? Well, the beauty is that's up to you. But I like to use delegation. Again, delegation, which we see pervasively used throughout the system. And I like delegation, again, because I get to define the protocol.

I get to define my delegate protocol that I'm asking my delegates to implement so I can call them easily. I can also use notification. Again, delegation is great for one-to-one, and notification is great for one-to-many. But that's up to me. That's my choice. In this example, in the demo, in the code you're seeing today, I'm using delegation.

And it's important to point out that you're going to see the usage of perform selector on main thread colon with object colon wait until done colon. This is used because typically most operations perform some tasks, calculate some values that are going to be used inside your UI. And UIKit can only be safely used on the main thread. Perform selector on main thread is used to move execution to that main thread so I can do UI updating. And you'll see how that's used very simply in the demo code.

Again, we're going to move execution to the main UI kit thread to do UI updating. It's very important to remember that. So, we've talked about that concurrency, but why is this in a discussion about architectural relevance? I think it's very important because as we move forward with these very modern OSs and devices, it's important that we remove the traditional sequential code flow.

You see it already with your application delegates. Application did finish launching. Application will terminate. You don't call those. The system is calling you. So, you already are starting to break up your code flow based on someone might or may call me. And the idea that we remove these dependencies on sequential code flow and break our code up using NSOperation is a very powerful design pattern.

We get a much more modern, much more flexible, much more dynamic architecture that lets me, again, reuse these components day to day. And as I talked about, since you're forced to refactor your code into this new paradigm, you get automatic code factoring, which I said, we want to break those things down into smaller and smaller chunks of code that makes our code much more reusable. So now I'm going to show you a demo on concurrency using NSOperation and NSOperationQ.

Let's look at NSOperation and NSOperation Queue in Action. What I've got is a simple little app here that displays a table view. And in the table view is a set of images. Now these images are a bit different because they're quite large. 1024x768, some are 1600x1200, 900x600. And you can see if I try and scroll that, that it's not really scrolling. If I flick, I don't really scroll, I jump. And the user experience is not very interesting.

So I have a little button here that's just going to turn on a switch that's going to let me use NS Operations internally. And all of a sudden you see this very different user experience. I can go up and down, and since I'm seeing my text dynamically, I can move very fast. And you see those images load lazily. And in fact, you saw there that they don't even come in sequence because I don't care about the sequence. They're just loaded on demand.

And so, and in fact, I can scroll and just wait here, and you can see that while scrolling, those are being loaded and indeed coming in. And so I have a very nice user experience. Now, in a normal app, you'd probably cache these once these were built to the proper size. But what I can do here is I can show you that I can do really interesting behaviors now in my app since I'm not restricted by UI constraints. I don't have to worry about those. So these are all being done in the background.

And I get a great experience. And in fact, I can turn on the clear background, and you can see that compositing performance is still excellent because all my real work is done in the background on NS operation. And you can see those come in, and I get really, and you can see they just jump in right there for me, and I don't have to stop doing anything. And if I want, I can even put a gradient in there and change the size again on the fly.

And you can see I still have excellent performance. There's a lot of compositing going on there. But since I'm doing all my heavy work on a background thread inside NS operation, it's really, really great. See those load lazily. Let's go to the code. See how we do that.

All right, let's look at the code for NSOperationTest. And you see, it looks like there's a lot of code. There really is not. This ImageHelper class is just for doing some of the scaling operations. And we can look at that real quick. And this creates a CG image from our UI image scaled. It's very, very straightforward code here. It's very simplistic.

But the important thing we want to look at is our table view and our NSOperationTest. So here's my test view controller. And what that does is it's a table view controller. It's the data source and the delegate for the table view. So in our standard self-heroid index path, we're being a good citizen. We're always trying to recycle existing table view cells. So I try to dequeue one here.

And if I don't, I create a new one. And this setup cell simply handles the case where I'm using a clear background or a gradient view. That's all it does. It's not pertinent to the example. And all I do is I say I request image for index and text for index. And this simply goes into my data model code and pulls out the text for the name of the image. So let's look at the important guy here, request image for index.

So if we look at that, here's my image state object. This is my model object. It's a very trivial object. And here it is. It has a path, a boolean, whether it has an image or not, and whether the operation is queued or not. These two are booleans. This is a string. That's the path to the image I want to load. And you can see my implementation, I literally only have a DL method.

So the rest is done by using properties on the image. And that's my model object. It holds the path and whether I have a queued operation and whether the image has been loaded. So if I'm not using operations, I simply call my little helper function, which says go out and load that image and scale it right there. That's it. That's all the code. It loads it and scales it on one function.

If I am using operations, I look at my model object and I say, do I have an image? And is there an active queued operation for this cell? If there is not, then I'm going to go out and create my load scale operation. I have my designated initializer in it with path, which is right out of the slide code. And I also pass the index, which tells me which row this image belongs to, obviously which table view cell row, and the target size that I want for that image, which I'm acquiring here, which is just based completely on the row height.

I simply set myself as the delegate, and then I add that operation to my queue, which I've already created when the view did load. Then I release it, and then I keep my model object in sync, very simply by saying, yes, I have a queued operation. This model object, remember, holds the state. So this tells me I have a queued operation. And then I simply return my placeholder image, which you see initially. That's all I have to do. Now, let's look at the load scale operation.

If we look at that, it's pretty straightforward. Here's my declaration. I have the string, which is the path to the image that I want to load. I have my resultant scaled image, my UI image. I have the index, which tells me which row in the table view that this belongs to, the target size for that image, and my delegate. So very, very simple.

I have my designated initializer. And that's it. If we go back to the code, here's my designated initializer. I just retain that path. I store the index and the target size. And the dealloc simply just release those things, release the scaled image if there is one. So here's the important part. Here's my main. Now here's where you're going to see something interesting.

I broke out this code. I could have used that same helper function that does the image loading and scaling. But to be a good operation, we want to test frequently if we've been canceled. And this is extremely pertinent in this demo because the table view is scrolling. It's creating these operations on the fly and then canceling them as soon as that operation scrolls off since we don't know if the user is going to stop scrolling or not.

So as soon as it scrolls off the screen, you're going to cancel this operation. So we want to be a good citizen and check frequently to see if we have been canceled. So like all threading, I create my own auto release pool and then I just start doing my work. I call initWithContentsOfFile, UIImage.

And then I test to see if I've got the image. And then all this does is calculate a scale factor so I get a proportional size. It's just some math. There's no big deal here. I get that scale factor, and then I say, all right, what is my new image size, width and height? I'm simply multiplying by the scale factor.

And again, I'm checking to see. I've done a little bit of work. Have I been canceled? No. Keep going. Lawrence Coopet So then I create my CG bitmap context, and I set a few parameters on that. And again, I say, hey, have I been canceled? Then let's keep going.

And then all I do is tell the CG context to draw the image into the new bitmap context, because that's going to give me my scaled image. And then I release and clean up. And even here, I say one more time, have I been canceled? No. Then let's go ahead and acquire the CG image that is inside that bitmap context.

Then I release everything. And down again, I look here and say, all right, do I have my CG image? Yes. Have I been canceled again? Then let's construct a new UI image from that CG image, release the original CG image. And then at the end, I drain my auto-release pool.

And again, I first test to say, have I been canceled? And if I have a delegate, let's send that message to the delegate. And here in my operation, We're going to look at the delegate, which simply says, here I declare my delegate protocol, operation finished scale. It's actually finished load and scale. But that's my delegate protocol. So let's close that.

I've looked at my operation. And indeed, in my NS operation test view controller, you'll see that not only am I UI view controller, I also subscribe to the UI scroll view delegate protocol, the table view data source protocol, the table view delegate protocol, and the table view data source protocol. And the load scale operation results delegate protocol. So I simply declare that I implement those protocols. If I go back to the code, once I do my load scale operation, you see it's very simple here. Let's look at the delegate.

So when that operation finishes, it calls my operation finish scale. And in fact, I'm going to check one more time. If the operation has been canceled, I just immediately return, right? I'm done. And here's the important part. Now, I test, am I on the main thread? NSThread is main thread. If we are on the main thread, because there's no guarantee what thread you're going to be called from, you could be called from the main thread or you could be called from the thread which your operation finishes.

Lawrence Coopet That's the beauty of this is that I don't and I shouldn't care. And so I ask, am I on the main thread? If I am, I go ahead and acquire the table of the cell for the row at index path. Remember, I store my index in the operation itself so I can recover that cell. And the really cool thing about this method is if that cell is off screen, it returns null. It returns a nil pointer. So I know if that cell is on screen simply by trying to acquire it and see if it's nil.

So if it's not nil and I have a scaled image in my operation, I just go ahead and set that. And then I tell it I need the cell to relay out because the image can be an arbitrary size, right?

[Transcript missing]

So we've seen the demo and how NSOperation and NSOperationQ can dramatically change the behavior of our application.

But it's important to reiterate a few points. NS operation and thread safety. As I said in the demo, UIKit can only be safely used on the main thread. That's why we have PerformSelector on main thread, so that we can move the execution from an arbitrary thread to the main thread, so you can do all your UI updating.

And you saw how easy that was to do inside the code. Very trivial. And it's important to remember to never modify UI state directly from inside your operation or any thread. UIKit is not thread safe, so remember, don't modify any sort of UI state from inside a secondary thread.

And as we saw in the demo, we want to transfer data ownership. We never want to share data between threads. It's almost always a bad, bad idea. We want to maintain that one object, one thread relationship. So you need to be wary of things like NSThread Detach New Thread Selector, where the target is self.

That means you're probably trying to share that object and its data on a secondary thread. Also, I see developers using NSInvocationOperation initWithTarget self again. When I see that, that points out to me that you might have an architectural problem and that you could run into thread collisions because you're trying to share your data and your objects between threads. So be careful.

So now I want to talk about working with system-owned view hierarchies. System-Owned Views, what are they? Well, all the SDK-provided high-level views, things like the Map View, UI Web View, UI Image Picker Controller, the Music Player Controller, these are built view controllers that are there for you to use.

But remember, these views are considered private, and the hierarchy is not documented and can and will change. But we understand that you may want to involve yourself with that functionality, and we have a supported way to do that without modifying or changing the view hierarchy underneath the system.

You can do that by overriding UI Windows Send Event. This allows you to insert yourself into the event chain and watch specific events as they come through and change the behavior of that view based on those events. But I want to stress that we want to use an event-independent mechanism to tie in on UI Windows Send Event. You do not want to take the event that's dispatched via Send Event and pass it through your system. Events have a timeliness. They have a lifespan.

So you do not want to send these things out throughout your code. So I like to recommend that you use, again, delegation. Delegation, delegation, delegation. Remember, you can then look at the event and pass it to a delegate without necessarily passing the event itself. There are some rules if you're going to do this.

One, you must call super send event first. If you do not do that first, you have stepped into unsupported territory. You can look at the event, but you can't touch it. This is important. That's why I talk about delegating and not dispatching the event directly. Look at the fields. You can look at the location.

You can look at the view that it's targeted for, but you should never modify those fields inside the event. It will not work the way you expect it to. And it's important to be quick about it. UI Windows Send event is called quite frequently, so you do not want to do long blocking operations inside your delegate if you're going to support this behavior. I want to step into a quick demo and show you how easy it is to do this.

So let's look at overriding UI Windows Send Event. What I've got here is a very simple test application that's watching all the events that come into this window. So if I triple tap, in the background, I get one alert. And if I triple tap in the button, I get a different alert. So all I want to show here is that I'm differentiating between taps in the window in different views. So let's look at the code for that.

All right, so let's look at how we're overriding UI Window Send Event. What I've got is my tap observing window, I'll call it. And you can see that it subclasses from UI Window. And all I've got is a few IVARs in here that tell me what view I want to observe and the delegate that observes. And these are just some housekeeping IVARs for tracking our multiple clicks. Again, there's my protocol that I'm going to declare, tap observer protocol, which says user tapped observe view with taps at location.

So if we go to the implementation of that, we can easily see what's happening. So here I'm overriding UI Windows Send Event. And all this code is really just to pass on based on the number of clicks. But first, the important thing, remember our first rule? I have to call super send event. Then I'm just doing some housekeeping to make sure that I actually have work to do, that there's a view to observe, and that I have a delegate.

And then the rest of this code here, all this is doing is making sure that we only care about multiple clicks. And all that's doing, so we're going to perform a selector with the forward tap, which is going to actually call our delegate based on if I have more than one tap count.

And that's really all the code is. That's all it's going to do. This is just handling multiple taps. That's all it's doing. And let's look how it's used. So in my application, did finish launching, what I'm going to do is for my window that I load, this is standard code you see in every app. It's going to add the view controller view to the window. And then I've declared this as a tap observing window in my interface.

And so when I go back to that, I know that I have these properties, delegate that observes, and I'm going to set my view controller to be the delegate. And the view to observe, I'm just going to set to be basically the whole view hierarchy, the view controller.view.superview. Very, very simple. And we'll look where this is being forwarded on in our test view controller.

And this is standard stuff, except for here's my delegate method. User tapped observe view with taps at location. And all I'm doing is if the tap count is greater than two, right, with triple clicking, that's all we're concerned with in this simple little demo, then I determine if I'm in the button view or not.

And if I'm in the button view, then I put up one alert. If I'm not in the button view, in other words, if I know where I'm tapping is in the button view. If it's not in the button view, it's just in any of the background views, I put up a secondary alert. So very simple to override the behavior of an existing view hierarchy.

That's good. Let's go back to the slides. So we've seen how easy it is to override UI Windows Send Event and insert myself into that event behavior chain. Next, I want to talk about UI Image Picker Controller in iPhone OS 3.1.

[Transcript missing]

So let's look at UI Image Picker Controller in iPhone OS 3.1.

What I've got is a very simple demo here. It puts up a toolbar, and we're going to go and pull up the UI Image Picker in the camera. You can see I'm live here. And what I can do, and what's important here is that this, you'll notice, is my UI. It's not the standard UI. And in fact, if I want it, I can have a view hierarchy that covers the whole camera region.

If I liked, with transparency, it will be compositive for you. It will work exactly as you expect, especially if you're doing an augmented reality app. In this case, I'm just going to start taking pictures every five seconds. And you can see that there's my picture taken. And we'll do another one.

And when I say I'm stop, it's going to take me back to my app and just go through those. It's UI image view just cycling through those images. So I have total control over what appears on top of that camera overlay and also control of the full functionality. I can take my own pictures. I can time it, do whatever I want. So let's look at how you would do that in code.

All right. So let's look at taking advantage of those new features in the UI image picker controller. And it's very straightforward again. And here's my method, show image picker for the source type. So when I tap on that button in the corner, this is what it's called. And the important thing here is there's some logic to determine if I'm animating those existing views, if I'm currently in my baseline image view. But here's the important point. I go out and I acquire my image picker. So if we look at that real quick.

Let's expand this code. I'm just asking for the generic UI image picker controller. I set myself as the delegate. But here's where it gets important. I load my overlay view. So I'm actually loading my overlay view from a nib. And in fact, this is another little tiny view controller, right? It's going to control that toolbar that we put in at the bottom of the screen. And this is a very nice architectural workflow where since I don't control the image picker controller, I'm going to put another controller on the views that I do control.

So again, a very important point here. So I can actually have two view controllers on screen at once. The view controllers, remember, manage views and the logic within there. It's not managing screens. But again, I set myself as the delegate of that overlay view because I have some behaviors I want to listen to.

And so when I go down and look here again in my show image picker, once I've got that image picker controller, then I go down and look here again in my show image picker controller. And so when I go down and look here again in my show image picker controller, go ahead and say, hey, is the camera type available? And then I'm going to say, let's instantiate, let's put that Overlay View into the Camera View.

And I just do a simple, quick test to say, am I already in the Image Picker Controller's Overlay View? And here's the important line right here. I simply say Image Picker Controller, Camera Overlay View is self overlay view dot view. That's it. I do some logic here to make sure I put my little Overlay View, my little toolbar view, at the bottom of the screen. And that's it.

Then I present that view controller modally. Now, if we look at the code for the Overlay View Controller, I have some very simple things. Take Photo, when I press that Take Photo button, it calls this method. And I ask the delegate to take the snapshot. Because you'll see what's important here is that if I'm already waiting for a snapshot, then I have to delay that.

But otherwise, I just tell the Image Picker Controller, go ahead and take a picture. Again, programmatic use of that new API. And that's really basically it. And here you'll see my Image Picker did finish picking media with info, which is what gets called when that image is available. And this is standard stuff. And you'll see some interesting things here, which is I still-- here's my code that I'm reusing again that gets the image, scales it.

And-- Yeah. --puts it into the captured images array that's going to be presented once I close this Image Picker Controller. So I have total programmatic control over the overlay views and how they interact with the camera. And of course, I am taking advantage of not having to hack any view hierarchies. And I still get the behaviors I want. Let's go back to the slides. So you've seen how easy it is to use UI Image Picker Controller in iPhone OS 3.1 and overlay your own views and take control of that view hierarchy.

Why are we talking about this in a session dedicated to architectural relevance? Apple has a huge investment in the frameworks and the APIs that you're using. So it's important to not only be a smart OS client, it's important to be a good OS client. And you want to avoid unsupported territory within the system.

Also, it's very important that you plan ahead. If things change underneath you, how will it affect your code? Will you crash or will you just elegantly drop that feature as you should? So you need to spend time with the documentation and understand if there's any other way that you can achieve your goals.

Is there a supported delegate or notification mechanism within the system to do what you like? And what I'd like you to take away from today's session is that it's important to know the system and the OS that you're working on. Read the documentation. Understand that you have API choices that can dramatically affect the functionality and the behavior of your application. And those choices can affect how you design your architecture.

Make sure you try to take advantage of the high-level APIs first because they're so simple and easy to use. But make sure that when you want to, that you know that you can go deeper within the system and there'll still be a fantastic, rich API set to help you build your application.

And experiment. Branch your code and look at alternate APIs and functionality and experiment and play with things within the system and figure out if they will benefit your application in a better way. Thanks for watching. We're looking forward to seeing your new and improved apps in the App Store.