iPhone • 1:01:04
Whether your iPhone app is in development or on the App Store, strong code architecture is an essential part of your daily process. Learn about good data modeling, communication between view controllers, and when to use delegates and notifications. Find out how to make important decisions about memory, speed, and a responsive UI. Developers of all skill levels can benefit from this thorough examination of iPhone SDK best practices.
Speakers: Matt Drance, Alex Aybes
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Welcome to the effective iPhone app architecture session. My name is Matt Drance, I'm the Application Frameworks Evangelist at Apple, and I'm going to be joined later by Alex Aybes. He's one of our illustrious iPhone software engineers. And you guys have learned a lot over the last couple of days here at WWDC. You've learned a lot about specific technologies and how to use them, the right ways and the wrong ways are for that particular API, how to be most efficient.
Before everybody starts throwing them back, I want to take a little while to talk about all the best practices all the thought processes, all the principles that apply to your daily activity as an iPhone developer. Regardless of which of these APIs, which of these frameworks you happen to be using.
Now you're all here because you want the same thing. Wherever you came from, whether you're here to fix a couple of bugs or adopt some new features in iPhone OS 3.0, or add new features to your own application - you're all at WWDC because you want to make the best apps possible.
And there are a lot of things that go into that, and we're going to be talking about that over the next hour, but you want apps that are responsive and stable, and maintainable; so that when the time comes to fix a bug or add a feature, take advantage of new technologies, you can do that quickly and easily. You want an app that can go months without updates. I think we all agree this icon looks a lot better without a badge next to it.
And so we're going to talk about the things that you can do to keep those things going throughout the lifespan of your application. Now we've spent the better part of a decade building these technologies, these frameworks, that you guys are now using to build your apps. Starting in 2001 with the original release of Mac OS X, all the way to today with iPhone OS 3.0. And we've learned a lot ourselves over that time. We've learned what works and what doesn't.
We've learned what the patterns are and the principles, and the practices that go into good app and framework architecture. And so we're going to talk about those things and how you can apply those best. Whether you're writing a library yourself, or just an application that needs to be maintained down the road. Now we all know that real life doesn't always follow the manual, and a lot of times things just kind of take a life of their own. So you're working overnight, you're working on the weekend, you throw something together, you bring it in, your boss loves it.
He says, well I'll add a couple of things; we're going to do a demo this afternoon. And then the demo gets approved, and the designer comes in and says - OK let's add some features, we're going to put a screen over here or over there. And before you know it, you've shipped the product.
[ Applause ]
And you're proud of it, it's a good product, it's selling well, your users love it, it does everything you wanted it to, but there's that one piece of code in there. Maybe more than one, that you wish you had time to take care of, and you think it's going to come back to say hello in the next couple of weeks or months.
So we're going to talk about those little pieces and how you find those, and how to address them, and how to avoid them, so that you can concentrate on what matters most; which is working on your next great idea. And your users can concentrate on actually using the applications, and not just re-downloading them all the time.
So there are 4 main areas we're going to cover today. The first is data persistence, how you process information in your app, and the decisions you make from the beginning, and how those decisions affect the rest of your app development. We're going to talk about the design decisions, both at a high level in terms of user interface, as well as your class design. And we'll talk about memory management, can't talk enough about memory management.
You've -probably heard a lot about it this week, and you're going to continue to. And finally we'll talk about all the other things in the application lifecycle, things around the system that affect your app, and how you can respond to those and work well with them. Let's get started with data persistence.
[ Silence ]
Every iPhone application processes information of some kind. And there are different types of information that you will be handing on a daily basis. There's user data - things like documents, downloads, media. Preferences - like settings, sensitive data, passwords, private keys - and of course cache or temporary data. When it comes to storing this stuff in memory at run time, we start to think about what we do on that weekend when we start to throw together that proof of concept.
Alright I've got a bunch of stuff to put on screen, just throw it in the dictionary, no problem. There's my work in the short term, but all of the sudden you've got all these string literals all over the place, you're using them in 1 place, you're using them in another, you decide well... what was the name of that key? I don't remember. You decide to change something down below, you've got to find all the places you did it before. This quickly spirals out of control.
You can define constants if you want, but this really isn't scalable, and there's no contract in place to really explain what the data is that's flowing around the application. So really take the time from the beginning to define proper data objects with properties, read write, atomic, nonatomic, retained, whatever.
But take the moment to define that contract so every part of your application that actually uses this data understands and knows what happens. This is not only more readable, but property access is faster than dictionary look ups. Both are constant time, but properties are faster and they're much easier to re-factor of course. This is much more clean, so this is madness and this is Sparta. Thank you.
So we've got data in memory, we've thought about the sort of things that our apps are going to do and use, and when it comes to saving that data you've got a bunch of options. You can put things in a binary property list, you can use XML, you can archive or encode those objects straight to disk with Objective-C, you could use SQLite for more complicated relational databases.
And now in 3.0 we have Core Data, which pretty much gives you all the power of SQLite with much less code to write, and much less stuff to worry about. So you've got a lot of options, and how do you choose which one? So first thing you need to ask yourself is, is this a user controlled dataset? And by user controlled I mean, is it a list of 5 that could easily be grown to 5,000? Can the user do that? And have you prepared for that? So if the answer to that question is yes, if it can grow indefinitely, you really want to take a hard look at Core Data; especially for a new project. If the answer to that question is no, if it's a relatively fixed dataset like an XML feed, a top 20 list or something like that, you just need to store it locally.
You can start looking at XML if it originated as XML, or binary property lists which is much faster, much more efficient, and lighter weight. But if the answer to that question is no, if it's not a small dataset, if despite the absence of user control it can still grow indefinitely, you really need to take another look at Core Data.
Core Data does all of the database work for you, it manages the object graph efficiently, as Alex will explain to you later, saves you a ton of work and gives you all the performance and memory management that you're going to want out of a mobile app. So that's the user data. Preferences are another story.
Preferences are managed by a single class called NSUserDefaults, and this is basically just a dictionary with a saved method, or a synchronized method. Preferences are for lightweight settings. And by lightweight we mean things like the cities that you enter into the weather application, or the stock symbols that you enter into stocks.
It's also for things like navigation state, so those of you who have used built in apps, you'll notice if I change cities... if you change cities in the weather app and then quit out, sometime later you re-launch the application, weather remembers what city you had selected. And that's just a simple integer index that's written out to the user defaults, but navigation state is a perfect example of something that belongs in the user defaults; because you don't want that necessarily preserved across, well you do want that preserved across restores.
Now this is a really convenient API. Like I said, it's just a dictionary with a save method, and it might be tempting to just throw a bunch of stuff into the user defaults. So it's important to remember that the user defaults are not meant for heavyweight data, and we've seen this a lot so we need to just say, you don't want to put things like images, or movie files, or sound files, or massive archived objects, that's user data.
User defaults are just for preferences, lightweight settings, navigation state, things like user names for example. But you don't want to put things like passwords in the user defaults. That's what the Keychain is for. The Keychain is an API that lets you store sensitive data - things like passwords, certificates, private keys. And your app can carve out a special section of the Keychain where you can store this data and leave it secure.
And in 3.0 you can actually carve out a shared space of the Keychain that all of your applications can have access to. So if you have multiple apps that sign into the same social networking service, for example, the first app asks the user to login, they do so, saves the password to the... user name and password to the Keychain. And the second app when it launches can just pull those credentials right out of the shared space, and the user doesn't have to login to get them.
So that's a great user experience, all while keeping the sensitive data secure. And finally I want to talk about cached data. Everybody knows what a cache is; I don't think I need to explain that. Save some stuff down... I'll explain it anyway. Save some stuff to disk, and it's important on a device like iPhone because... especially when battery life is a concern, you don't want to have to re-download things like buddy list, avatars, and stuff... image thumbnails, things like that. Caching becomes important on iPhone because iTunes is constantly backing up all of that data. So we have a special designated caches directory that you can find the constant, is called NS caches directory.
If anybody's not using that, go ahead and jot that down. If any data that's saved to the caches directory is not backed up by iTunes, so those 500 image thumbnails are suddenly left on the device, so everybody uses the caches directory effectively then we can all dramatically reduce the amount of time it takes for a user to back their phone up to iTunes.
And that's you guys contributing to a good user experience, even when the user's not using your app, even when they're not thinking about your app. So when you go home after this week, take a good look at the stuff that you're writing to disk and think about the caches directory, and how you can make better use of that.
That's it for the data part of the talk, now I'd like to bring Alex up to talk about the design decisions you guys will be facing.
Thank you Matt. My name is Alex Aybes, I'm a software engineer on the iPhone team. And before I get started, I want to ask you a question.
How many of you have been in this situation where it's Thursday afternoon, it's about 4, you're pretty excited because that product that's due by the end of the week - tomorrow... well it's pretty much done. You're excited. You're going to be on time and you have those great plans for a fantastic weekend in the wine country, that's where the sun's been hiding all week. So that's good.
[ Laughter ]
... and goes, we need to change everything. All those panels you have, that last one, well it needs to come to the beginning. This other one needs to be presented modally, and all those things. At that point you are thinking, oh... no more weekend in the wine country, this is going to be the office all weekend.
So today I want to talk to you about a few techniques, and a few things you can do from the get go, from the very beginning to make those massive changes, those last minute changes because we all know they happen, make them take less time so you can still enjoy your nice weekend in the wine country or wherever you want to go this weekend.
Let's talk about that. How are we going to talk about that? Well 2 things I'm going to cover: the first one is going to be a quick recap of how you build your application. Most of you are probably familiar with this, I won't spend too much time on it, but I just want to make sure we're all talking about the same thing. The second point is going to be about how you sort of make the information, the data flow into your app, and through all those screens we're going to get. Let's take a look at an existing app on the phone, in this case the Contacts application.
The Contacts application. So you're probably all familiar with it, it's in the Contacts, it's in the phone app. And a lot of the apps on the phone are doing this, and because the phone is a pretty small device it fits in the palm of your hand, you really need to focus what you're showing to the user.
And if it's Contacts, you've probably seen it, we show the groups first then we move over to the list of contacts in that group, and to an individual contact, you can navigate back. So it's a really focused set of data and we're displaying 1 at a time, and switching screens as we go.
The user can drill down to see more detail. So it's good, that's the Contacts application. Let's take a look at another app that I particularly enjoy, because it talks about food. So it's a Recipes application. You might remember this from last year or from the samples that I actually published on the website, this year we made it better.
We actually rewrote it using Core Data. But it's the same basic principle. On the right side, left side, whichever one it is for you... on that side you have the list of recipes, in the middle you have the detail, so the actual detail of that recipe and then you have the photo of the final delicious looking product. That was really good. So this is a pretty simple app.
We have 3 screens, we'll end up with 3 controllers for those 3 screens - a List Controller, a Detail Controller, and a Photo Controller. Fairly straightforward. How do we implement those controllers? Like I said, probably most of you are familiar with it if you've started developing on the iPhone, or if you've been doing it for a while... use UIKit's UIViewController class. It's a pretty simple class; you use it, each screen you're going to get one of those UIKit View controllers, or UIViewControllers. You're going to subclass it, add your own application logic in there, and then use that as the basic building block for your application.
So what's the role of that view controller? At the minimum it's going to load views, that's what actually is going to make those views appear on screen. That's good. Then it's going to also connect your model, your data. In this case, those recipes. It's going to connect them to the views that you're displaying to the user, to bring all that data and show it to user. Probably most importantly it's going to manage the flow of your application.
What I mean by that, it's sort of like the chef in the kitchen. It's taking all these ingredients, making dishes out of it, and making an entire meal out of it to present to the diner in that case. So good, it's making me hungry. So View Controller's role, great.
So now we have all those basic view controllers we can build and show to user. We still need to assemble them, and for that there's another class that UIKit provides, the UINavigationController. That's a pretty standard class, it's used in a lot of the apps, and it measures the stack of view controllers.
It will manage a lot of the animation throughout the application, as well as the updating of the Navigation bar, and does a lot of things for you. So if you haven't actually used it, take a look, it's good. And that about covers all I wanted to say about application flow. So let's take a look at the really interesting stuff. How do you, now that you have all those screens and all... this application built.
How do you actually put the data in there? So we're going to take a look at the model, and the Recipes application. What is it? We have a bunch of recipes, each of those recipes has a bunch of ingredients, a set of ingredients. It has a set of instructions, has an image, and also has a larger image. So OK, you see that matches nicely to the 3 view controllers we have. So now we have these 3 view controllers, we have this model.
How do we make them talk to one another? Well view controllers, model, we can just go each and every view controller goes there and grabs whatever they need, and looks something like... whoa, like this. It's not so great. It's a lot of hands in the same little cookie jar. And if you've ever done this at home, little dinner party, invite 5, 6 friends who love to cook... it's usually a disaster. Everyone's... needs the oven at the same time, and you end up not eating until 3 in the morning.
Instead the only way I've found it to work is when I tell each of them, OK you're doing this, you're doing that. Malcolm is doing the salad, Evan is doing the meat, I'm doing the dessert - strawberry torte. And Matt, well Matt's really good at tasting so that's what he's going to do.
So we're going to try to apply the same idea to our application, and it's going to look hopefully more like this; where we're actually going to tell each of those view controllers what they're supposed to do, and what they are supposed to look at in our model, hopefully making less of a mess.
So how is that going to flow through our application? The first thing that gets invoked in your application, that's the Application Delegate. That's the first thing that gets loaded, so makes sense to have it load the actual model, and that's what it's going to do. It's going to load the recipes.
It's also, since it's the first thing it's actually going to create the first view controller, in this case the list controller, and give it the set of recipes. Now when the user actually wants to drill down and see an actual recipe, this list view controller is going to get that recipe, create the next view controller, and pass the recipe along. Same thing when you want to view a photo, it's actually... the detail view controller is going to take the photo and pass it to the next view controller that it creates. Alright, fairly straightforward.
Let's take a look at what's in the code in the Recipes application. By the way that sample is available for you to download. And the application did finish launching, we create the view controller, and set up that first view controller by assigning the managed object context. The managed object context is sort of the entry point in Core Data, when you're loading objects from Core Data that's the basic starting point.
That's where you're going to get all the objects. And self managed object context is where we actually instantiate it. If you want to look closer at the code, it's available for download. Alright, that's pretty good. So let's take a look now at the next step, when these are actually launched just like the recipe.
So it all starts with a user action, the user selects something... again that strawberry torte. So when you get when the user selects it, as table view, sends a message to the delegate, select, roll it in.... so the first thing we're going to get, we're going to do, is actually figure out what recipe it is. For that we have this interesting fetched results controller that's new in Core Data for the iPhone, and I'm going to talk a little bit more about it later, but it's a really nice class you really want to check out if you're doing Table Views in Core Data.
So we get that recipe and then we're going to create the next view controller. And once we have that view controller created by simply allocating and initializing it, we set it up, in this case just passing the recipe. Again, the recipe is the only thing it really needs to know about. It's the only thing it really cares about. So we send that recipe, push the next view controller on the stack, and forget it, release it.
You don't really need to hold onto it, it's pretty good. So you don't need to hold onto it, but in some cases you actually do need to not have quite forgotten about it. What happens when the user tries to edit that recipe? Let's take a look in what it would look like in the UI.
You have the list, in this case we're going to get the chocolate cake, so I had the chocolate cake, he had the chocolate cake, he decided that it's not really the chocolate cake, that name is not quite doing it justice. So instead we're going to rename it to triple chocolate cake, because triple is better than single in something. So we edit it. At that point we need to update that original view controller with the list, because it needs to reflect the new title.
So how are we going to do this? Well the list view controller needs to be told that something needs to happen so that it can go ahead and reorganize the list, now that triple chocolate cake is at the bottom of the list. One easy way to do it, have a pointer from the detail back to the list controller, and at that point just go reload the data.
That's great until 4 p.m. Thursday evening when your boss walked in and went - oh, actually we're going to add something in between, and it's not quite going to look like that. But at that point all those 2 view controllers are linked to one another, and you don't know how to separate them.
So instead of pointing directly back to the parent, we're going to use delegation. Delegation's a pattern you're probably familiar with if you've been developing on Cocoa on the Mac OS X desktop, or Cocoa Touch on the iPhone OS. It's used throughout, and it's a very simple way to communicate while still preserving a certain amount of decoupling.
Let's take a look at how you implement it. Delegating, that's going to be the side of the detail view controller. That's the one that's going to be doing the delegation. And the first thing it needs to do is declare a protocol. To declare a protocol in the header you just declare the protocol, so in that case we're just going to need a single method that just says, hey the recipe detail view controller actually changed the recipe; fairly straightforward.
The next thing you need is it to actually have a property for it, so a delegate property. In this case, we created the property, the recipe detail delegate property. 2 things I want to point out here: the type of that property, it's just a generic object, any generic object that conforms to the recipe detail view controller delegate protocol. And this is how we are doing the separation between the 2. At that point they don't know, they don't need to know what type the parent or the delegate in that case, what type the delegate is.
It just needs to... all it cares about is that it implements that 1method in the protocol. The other thing, typically delegates do not retain... are not a retaining property, so it's just an assign in this case, and that's important because typically you'll have the parent of the delegating object will be the one listening. So it will be the delegate, and since the parent owns the child, if now the child owns the parent through delegation relationship... retain cycles, memory leaks, bad.
Alright so we have the recipe detail view controller side, now to the recipe list controller. Bring a delegate. What do you need to do? Implement the protocol. So you need to declare that you conform to the protocol first in the header, it's pretty simple. And next you need to actually implement that single method in there. In this case all we really need to do for the Recipes app is to just reload the data, it's straightforward.
You might have more to do in your actual application. So we're almost there. The last thing we need to do is actually tell the detail view controller that we care. So we need to set the delegate property we got there. That's just an extra line when we're setting it up, detail view controller, right after we assign the recipe; we're actually going to set ourselves as the recipe detail delegate. With all that done, we should be good.
There's 1 thing to note though about delegates Sometimes you'll get random crashes in your delegates, and that's usually because since they don't retain the delegate, the delegate might go away before the one delegating. So in this case if the recipe list controller somehow got deallocated before the recipe detail view controller, you might have a problem if the recipe detail view controller wants to message it through the delegate property.
So to avoid that, because that's a very common crash, I've had it a few times... just remove yourself from the delegate chain in that case dealloc. So in this case in dealloc detail view controller, recipeDetailDelegate equals nil, just tell the detail view controller you don't care anymore - you're gone. Good, so that's one way to do this messaging and still decoupling.
There are a few other ways you can do this. The first one is through notifications. Pretty simple, we have NS notification center. You can add and remove observers through this. And the typical use case for this is when you have 1 object that wants to tell many other objects about changes. Typically maybe you're downloading data in the background in the background thread, and you go OK, I got all my new data I want to tell everybody. So notifications are good for that.
One little thing to worry about with notifications, just like delegates, observers are not retained so... in your dealloc again, remove yourself as an observer so that you don't crash again when something you're trying... to post communications to things that have been deallocated. So that's notifications. The last method for doing this communication is Key-Value Observing, or KVO. This has been available in Foundation for a while now, and it's very handy, it's a really handy little way to observe particular properties.
For example your model object, if you want to know when the list of ingredients changes in that recipe, or when the name changes. You can say that you want to observe that particular property in the recipe object, and you'll get messages when that property is changed, and just when that property is changed.
So it's very handy. So that's good, 3 different ways of doing communication. Now let's take a look at how this is actually done in the Recipes app, because I presented the delegate approach to it, but it's not quite what we're using in the end in the Recipes app.
So let's take a look at how we did it. So we have the list controller. That list controller got the managed object context from the app delegate at the beginning, and it also creates an NSFetchedResultsController. That's what I'd mentioned earlier, it's this class that's available now in iPhone OS 3.0 in Core Data that does a lot of work for you.
So if you have a table view, if you have objects in Core Data, take a look at this. It will do a lot of caching, a lot of pre-fetching, a lot of really nice stuff that you don't need to worry about that. That fetched results controller points back to the managed object context to actually load the object from somewhere. And that managed object context, well, loads the objects - the recipes in this case.
Alright, so that's how it works, in the app, display objects, how do we actually get the communication from the recipes back to the list controller? Well when the name of the recipe is changed, the managed object context hears about it. How does it hear about it? Through KVO, through Key Value Observing. The managed object context automatically, when it loads the object, registers for notifications from them or starts observing these objects through KVO. So if we change the name, the managed object context hears about it. Next we need to actually tell fetched results controller.
And how does that happen? Well through notifications. Why through notifications? Well mostly because typically you'll have 1 managed object context that gets passed along through your different view controllers, and you have possibly 1 fetched results controller for each of those view controllers. So 1 managed object context, many fetched results controller, it's a good use of notifications in that case. And finally we need to tell the list that something's changed, and we do that through delegation.
Voila. All 3 of the methods I was describing are actually being used in the Recipes app, and while 2 of those you don't really need to worry about, notifications and KVO, because they are actually done under the hood by Core Data for you... you can still have similar techniques and that really illustrates well use cases for all those 3 methods.
So it's good, now you have use cases and you know about 3 different ways to sort of separate those view controllers, but really why should you bother? It's so much easier to just go reload data on your parent. Well the first thing you're going to get out of this is, reusability.
You're going to be able to take those view controllers and put them anywhere. That also allows us to provide you with view controllers that you can just reuse out of the box, like the Person View Controller, or the People Picker, or the Photo Picker. All those view controllers and many more that are available now in 3.0, are there for you to use and make use of notifications, of delegation, so that you can just insert them in your view controller stack and then no need to worry about what's going on.
When they need to tell you something, they'll use delegation or notifications, and that way you can act appropriately. So that's great. We can give you some view controllers to reuse. But it's also really important for you, because I imagine most of you probably want to write more than just 1 application.
You probably want to write multiple ones, keep pushing more apps in the store, seeing those revenues grow. So reusability will actually allow you to write your view controller for the first app, and then reuse it in the second app. And if they don't know about the previous view controller, the chain of view controllers, all the better.
You're actually going to be able to just put them in there. So that's great, that's 1 thing you get - reusability. You're also going to get reorganization, the ability to move those view controllers around. That means mostly because the view controllers don't know about their parent. That means you don't have this really tight chain of view controllers.
Instead you can actually just grab the delegate implementation, move it to another fav, or re-implement the delegate and now you can just reorganize your view controllers. What that really means is that on Thursday when your boss comes in, you can go yeah, that's fine. I'll do that tomorrow. And Saturday, Sunday you still get to go to the wine country - enjoy the sun that you've been missing all week, do whatever else people do in the wine country.
So it's good, good thing. It's a great weekend, nice, sunny, and everything. Now it's Monday... need to talk about memory. No one really wants to talk about memory, but everyone's going to have to talk about memory. It's an important subject, especially on such a small device as the iPhone, the iPod Touch.
So what are we going to cover? Can I remember what we're going to... yes, memory problems! That's right, memory problems. Sometimes it happens. If you can't remember your slides, that's a memory problem... but it's unrelated to your application. So 3 things we're going to cover: the first one, how do you identify the problem? How do you figure out, oh this could be a memory problem? Then I'm going to talk to you about a few tools that we provide to help you with those, solve the memory problems when you figure out you have some. And finally we're going to talk about some techniques you can use with regards to your model, your views, your controllers, and to help you reduce the memory footprint. So let's get started.
How do you figure out you have a problem? Well a good hint is your user is calling you or emailing you, or however they're reporting bugs, and complaining that the app just quits all the time in random places. You can't quite track down... it's not that particular download, it's not that particular account, it's not that particular screen, it seems to be all over the place.
That could be a good hint that the system is running out of memory, and actually quitting your app, and to the user it's exactly the same as a crash. As a matter of fact, it will actually produce a crash log, and now we're giving you the crash logs so you can look through them. And 2 things to look out for in the crash logs... no backtraces, a good hint that we actually just quit your app because we didn't have any more memory. And the other one, messages in the console talking about memory warnings.
That's a good hint, that's a memory warning. So now that you know you have a memory problem, how do you fix it? We have a few tools. So those tools, the first one I want to talk about is Instruments. 2 things in Instruments that are a great help, 2 instruments in Instruments that are a great help. The first one is Leaks. Leaks are what happens when you allocate memory, retain it, stop using it, but yet forget to release it, to free it, to deallocate it.
It's bad. The system doesn't know you're done with it, and it can't reallocate it, reuse it for anything else. So the Leaks instrument, well it actually helps you track down exactly where this object was retained, where it was released, who is still holding onto it, etc. It's a very nice tool to actually track down leaks in your application. The second one, ObjectAlloc, is more for the lifetime of your app. It will help you graph your memory usage, how it goes and how it increases and hopefully decreases sometimes, throughout the lifespan of your app. So it's a really, really nice tool.
It will really tell you with great detail where you're using memory. I'm not going to talk too much about those, there are great sessions. One right now in mission, don't leave please. You can check it out in the video later. And another one, which is today actually at 5 p.m. which you will also be able to check on video, so you should just stay here for the next session.
So great tool, Instruments. Check it out. The other tool I want to talk about is the Simulator. Why the Simulator? Well it simulates things, and I've been talking about memory warnings and that's one of the things you can simulate. It's quite difficult to actually reproduce a memory warning on the phone itself while you're debugging it, because you don't have control over what's going on in the background. You could try to load 16 million pages in Safari and check your mail 14 times, it can be quite difficult. Instead you can just use a simulator and simulate, good.
What do you do once you simulate a memory warning? Well use Instruments. It's really good. The ObjectAlloc in particular will help you track down how you're using memory, and hopefully how you're releasing that memory once you get a memory warning. How the system... to prevent it from quitting you. So that'd be good. And if you're not releasing that memory, again ObjectAlloc will tell you who's still holding onto it. So it's quite handy.
The last tool I wanted to talk about is new in Snow Leopard, it's really, really nice. It's the Xcode Static Analysis tool. If you haven't checked out Snow Leopard and Xcode, I recommend you do it. It will actually point out a lot of potential mistakes in your code, potential errors, and in particular retain-release problems.
It will actually tell you - oh this object doesn't seem to have been released. So once you see that, you can go inspect your code, because in the end it's a tool that will tell you what it thinks, but you are the ones who know your code best hopefully, and you can actually go in and fix it.
I found out about the Xcode Static Analysis tool fairly recently and actually I got so excited I wanted to do a demo, so I'm going to do just that. Beautiful. Alright, so this is the Recipes application. If I run it... looks like all the screen shots that I showed you, and I'm going to go check out the strawberry torte.
I made that a couple weeks ago, it was really good... still remember it. So you see, it kind of looks like last year except it's Core Data, so it's better. Now we're going to go to Xcode since I heard about this great new tool, I want to check it out.
And to check it out we're going to go click on the Build menu. Under the Build menu you have Build and Analyze, I'm going to run that... and now, you probably can't see this, but in the lower corner here there's a little icon, blue icon with 1 thing and it says... potential leak of an object allocated on line 137 and stored in 2 types.
If you drill down into it, it will actually show you the path the static analysis tool took to determine that leak. So it'll tell you oh, it went there and then continued on and on, and then on and then on... Oh and here, an object was allocated - the NS Array, alloc init. And here returns an object with a plus 1 retain count.
So far so good. And at the end, well that object has never been released. Object allocated line 137 and stored in 2 types is no longer a reference after this point. When you examine the code you go oh yeah, I'm just forgetting to release it am I not? So we're going to fix that. And then, still running... anyway, actually just going to go ahead and rebuild and analyze. And now we're good, so green.
Alright, so that's the very quick overview of some of the things the Xcode Static Analysis tool can do for you. I highly recommend you go check it out. So the last thing I wanted to cover is a few tips and hints on some things to do with your model and your view controllers. First of all the view controllers. You don't really need to cache them. They should be pretty lightweight, and the time to load them in your application really very small. So I'd highly recommend you don't cache them.
This is where the system, and typically the Navigation Controller, when you're not dealing with that view controller anymore, can release it and you're done, you don't need to worry about when to release your cache, when to unload things, when to refresh things, etc. So I'd highly recommend you consider not caching them.
If they take a long time to load, maybe there's another problem. So investigate that, Instruments again, really good to investigate what's actually taking all that time to load the view controllers. Respond to memory warnings. I repeated memory warnings quite a few times now? So don't forget to respond to those memory warnings.
It's a good hint that you have very little time to actually do something about memory before your app quits, and to the user essentially just crashes. So respond to them, and that's pretty easy. You have a method in view controller, did receive memory warning. One thing I need to emphasize here, when you implement that method, do not forget to call super.
Super, the default implementation in this case of did receive memory warning in UIView controller, will unload your view. And that's actually quite a fair amount of memory if you have a deep view hierarchy, if you have a lot of images in there; it'll unload your view and let go of all that memory. That happens when that view controller is not on the screen. We're not actually going to go ahead and remove all the views from the screen, and to the user that would be bad.
So all the view controllers are not on the screen, get their views unloaded. And once that's done you can actually do additional clean up. For example, if we are viewing the detail view controller, the list controller could actually just let go of the entire manage objects, in this case all the recipes it's viewing, doesn't really need them, when it comes back just re-fetch from the database. Nice way to clean out the memory. So if you receive memory warning, as I said UIViewController, unloads the view.
What this means for you, very important, is that the view's gone and now in 3.0 you can actually implement viewDidUnload, it will tell you that it's gone. That means that if you are... if you have outlets, if you have pointers, to any of the subviews of that main view controller view... let go, get rid of them, A, it's probably good for memory at that point. But B, they're probably not valid anymore since they're not in the view hierarchy, since their parent is gone. So get viewDidUnload. Important to implement.
So that's view controllers, now the model. Spread that model thinly throughout your view controllers. If every one of the view controllers in the Recipes app is holding to the entire model, when the memory warning comes in, can't really do anything because everyone's holding onto everything. So instead, if the recipe detail is just holding onto the 1 recipe it has, the photo is just holding onto the 1 photo it has, then you can get rid of the previous view controllers, the ones that are not onscreen, can actually let go and release all the model objects that we're holding onto. And you're good, you're saving memory, and hopefully you won't get quit after that.
The next thing is Core Data. Another great advantage of Core Data with memory, the first thing it can do partial object graphs. What that means is that in your first list view controller, you can ask for all the recipes. It'll give you an array that actually doesn't actually contain all the recipes. The other thing, it will give you recipes and if you tell it, well I'm really only interested for now, I'm really only interested in the name, the description, and the thumbnail. That means the gigantic 12,000 by 20,000 pixel image is not already loaded in memory.
Instead Core Data will nicely load it for you later when you ask for that particular image. I would not necessarily recommend 12,000 by 20,000 pixel images, not a good idea. So it will do automatic defaulting of all these properties it didn't fetch at first. And last but not least, it will respond to memory warnings. It's a good citizen. So the response to memory warnings gets rid of a lot of the cache that it maintains for you to accelerate things, and will automatically reload a lot of those objects as well. So it's very nice.
So if you haven't already used it, check out Core Data. Just to summarize, as much as we all hate to be figuring out what memory leaks I have, and why my application's using so much memory and gets quit half of the time, it's important. So it's... small devices again, and every time you run out of memory your app's going to get quit.
To the user it's just another crash, it's a bad review on the App Store, bad things. We have a lot of great tools and new tools in Snow Leopard, so if you haven't already used them... Xcode Static Analysis is awesome, Instruments, really cool. So check them out. And finally that can be overlooked fairly often, but the way you actually structure your code and structure which view controllers really get what parts of data, makes a huge difference with regards to memory. So don't forget that.
So these 2 bits, the memory management and the design decisions, are really, really important. I mean everybody understands that you don't manage memory properly, you're going to crash. That one's easy. But these design decisions are really important.
I hope Alex has explained the importance of that coupling and the proper separation. A lot of it sounds text-booky, but you really want to take the time to define delegates in places where there's going to be transmission and flow of information. It's going to help you cope with those re-designs that will happen.
A design is an iterative process; we've said that, you heard Eric say that in the UI design talk, you heard Brett say it in the prototyping session yesterday. So taking these steps, taking that little extra time in the beginning, will help you adapt to changes, and it will also help you do things like use the same view controller in multiple places, some more flexible code is going to be reusable.
The WWDC app certainly went through a bunch of changes before it ended up in your hands. I can promise you that it would not have ended up the same way if we weren't following these practices. So I'd like to finish up by talking about all the other things that happen during the lifespan of your app, both inside it and coming from other areas in the system and how you can work with those, and adapt and respond to them appropriately. So 3 things we'll talk about: interruptions, which are pretty straightforward, concurrency threading, and compatibility.
Compatibility's been a huge topic since iPhone OS 3.0 came about, and it's going to continue to be important as it ships and as the next release makes its way into the world. So starting with interruptions... Interruptions can come from anywhere. There are all kinds of things that can happen that can steal focus from your application. The phone could ring, you could get a calendar reminder, you get an SMS or an MMS message depending on your carrier.
[ Laughter and applause ]
And now in 3.0 you can get a dialogue from the push notification service. These things all come from applications from contexts that you have no idea about. And not only can they steal focus from your app, but if the user takes the according action, they can actually terminate your application. So understanding these interruptions - where they come from and how to deal with them, is very important.
So there are 3 delegate methods that your application delegate receives. Most of you have probably seen this before, but I want to revisit it because we've seen a lot of apps that aren't doing this. The first one you get when an interruption occurs, your app delegate receives "application will resign active". And this means that the phone is ringing or one of those dialogues has come up.
As soon as you get this message, you want to pause whatever you're doing, especially in the case of a game - I'm fighting the boss, I don't want to die by the time I've rejected the call - and you want to save any information that was important. This is where you want to do that. Preserve all your state, save all the user data, anything that was important, take care of it here. Because there are 2 things that can happen from here.
The first is the user could dismiss the application, excuse me, dismiss the interruption. In that case you'll receive "application did resume active", which means everything's back to normal and you can proceed. In the case of a game where it was paused, we recommend you keep it paused and just let the user decide when he wants to start playing again.
And if the user accepts the interruption, you receive "application will terminate", and this is the same message that you receive when the user presses the Home button. And this means it's over. Notice it's "application will terminate". It's not "application might terminate", it's not "application will terminate later", it's not "application may I please terminate". It's "application will terminate", and there's no time span associated with this either. This message is really just a courtesy. You don't want to do anything important here, because you will not have the time to get it done. Anything important needs to be done when that interruption occurs.
It's very important to understand that. So let's move on to concurrency. As these devices get more powerful, and your apps get more sophisticated, you start doing more work and you start thinking about how you can offload that work, or postpone and delay it so you can have a more responsive user interface.
And when we say concurrency, when we say offload, we start to think about threads. Now luckily there's a lot of API in Cocoa Touch that can allow you to perform these asynchronous tasks or offload busy operations, without having to worry about threads manually - things like NSOperation, NSOperationQueue, you just pass it an object and a selector and it goes ahead and does the work.
NSURLConnection has an asynchronous mode where you set a delegate and you just receive callbacks as data becomes available, as the download completes. That's done in a background thread, but you don't need to worry or even think about the threads that are associated with it. And there's a lot more available in Cocoa Touch, so before you go ahead and start diving down into manual synchronization and thread management, take a look at all the high level APIs.
I like to think that we make a lot of things easy in Cocoa Touch, so if you find yourself writing hundreds of lines of code to perform a relatively trivial task, you should stop and say hmmm... maybe I'm missing something. We've got the developer forums now where you can ask your peers and Apple experts questions.
Take the time to figure out what the right way to do things is. And regardless of whether you're using these asynchronous APIs, managing threads manually, or just doing it all in the main thread, make sure you illustrate that activity to the user. Constantly keep the user informed of what's going on, and there are a couple of APIs that make that easy. UI Application has the network activity indicator Boolean, which activates that status spinner in the status bar. We've also got the generic UIActivityIndicatorView, which is the spinner that you see in places like mail and the toolbar.
And we've got the UIProgressView which lets you do that progressive... download progress or anything else that has a deterministic start and finish. When we're talking about concurrency we're talking about threading. Eventually this work that you're doing will probably be reflected in the user interface. So when it comes to updating the UI, you have to remember that UIKit is a single-threaded environment. Everything that UIKit does happens on the main thread, and it's your responsibility to make sure that that contract is fulfilled.
And the delegate messages that you receive from UIKit are always called on the main thread as well. Now for you, if you're doing something in a background thread, you've got an operation and an operation queue, you need to update your UI. You need to make sure anything you do there is wrapped in a call to performSelectorOnMainThread.
This will bump you onto the main thread's run loop. And if you're not sure where you are, you can just ask NSThread, am I currently on the main thread? So 2 very easy APIs, not a lot of work, that can ensure that you're going to do the right thing.
Anything with a UI prefix in it needs to be called on the main thread. So go home, take a look and make sure that there's not a potential for you to be calling stuff on the main thread. If you're getting away with it right now, don't take that for granted. Go back and take the time to do the right thing, otherwise you may end up with some crashes down the road.
So we've been talking about threading, we're obviously concerned about performance. And when it comes to UIKit and proper performance and drawing speed, there are a couple of things that you can do to... that are very simple, that you can do to make sure that your UI is performing and operating the way you would expect.
The first thing is you don't want to call the loadView method on UIViewController. And most of you probably know this, but we've seen it. If you need to get to the view controller's view, just access the view property. If the view is not in memory, it will call loadView for you. load View is something you implement; it's not something you call.
If you call loadView and the view's already loaded, then you basically, you could potentially blow away a whole bunch of state that's already there. So always use the view property. Similarly when you're drawing, if you have a custom view, you subclassed UIView or something else, drawRect is a method that you would implement to do your custom drawing.
You would never want to call drawRect directly. I'll tell you why. Let's say you've got a delegate method, you've got a delegate message that you received and you need to update a bunch of items in a specific view. If every one of those updates, let's say in the same run loop iteration, you call drawRect 3 times. We will draw 3 times in that same iteration. The user probably won't even see the difference, but we've executed 3 drawing operations. What you really want to do is call setNeedsDisplay, which is effectively a dirty bit.
It requests that the view redraw itself. So you can call setNeedsDisplay once, you can call it 10 times, and basically what that means is in the next iteration of the run loop we will draw the view once with all the new state that you may have set. So go ahead and look through, make sure you're calling setNeedsDisplay everywhere you should be - same thing with layout. You want to call setNeedsLayout, not layoutSubviews. layoutSubviews is what you would implement, setNeedsLayout is what you would call.
So these are simple things that can help really boost the frame rate of your application, so take a look and make sure you're not doing a lot of unnecessary drawing or layout. And with these sort of best practices about do's and don'ts, we come to compatibility. And we've seen a lot of compatibility stuff as we transition to iPhone OS 3.0. It's been a big topic, you've seen it in email, you've seen it on the Dev Center, seen it on the forums.
There are a lot of things that you can do to make sure that your app remains compatible, not just now but for the foreseeable future. And the first one of those is using class name prefixes. You've seen this in all of our APIs and all of our frameworks, and a lot of you might be... a lot of people seem to think that prefixes are just for APIs.
Nobody's using my code, I don't need to add a prefix. But there's still a problem here. Namespace collisions are a fact... are a reality in any environment, any programming language. Objective-C and Cocoa Touch are no different. So let's say I have a session class that's in my application. I'm using this third party framework that also happens to have a class called Session. Once I build that, I load that binary and there's all this code asking for session objects.
The run time has no idea which session object we're referring to. The fix to this is very easy. You just add a prefix to your class, and you've immediately differentiated your session class from the other guys. This is a must. Anybody who's not doing this right now, especially with a generic name like Session, go back home... and change your class names to have prefixes. Whether you intend to release the code or not, this prefixing is very important. Same goes for method names, particularly the use of underscores.
Underscores are reserved by Apple for private method names, and some of you may have seen sample code out there on websites or something that uses underscores. They think they're following convention; you don't want to use underscores. I'll tell you why. Let's say you subclassed UITableViewCell, and happened to have declared a method with an underbar prefix. You have no way of knowing whether or not UITableViewCell itself has a method with the same underbar prefix. And so what happens now? Now we've got some internal UIKit code that calls this underbar method, expecting that nobody's overwridden it.
But instead of the UITableViewCell method, it's going to call yours. And this is going to result in a whole bunch of unpredictable and unexpected behavior. So stay away from underscores. And if any of this stuff is affecting you, if you've forgotten to prefix your class names, if you're using underscores, got a wonderful tool named Xcode. It's got refactoring support, so it should be fairly easy for you to adapt to this guidance. But that's one of the things you want to go home and take a look at for compatibility reasons, not just for 3.0, but for everything else down the road.
And supporting new features in certain situations is very easy as well. There are a lot of classes in 3.0 that have new methods in them, and finding out if an existing class like UITableViewCell has that new method in the new version, is very easy because of the dynamic run time of Objective-C, you could just ask the object if it responds to a given selector. If it does, send it the message or perform the selector. If not, just proceed as you would have in previous versions. You don't need to do any kind of crazy pound defines or version checking, or anything like that.
Just ask the object what it's capable of doing We're just about out of time. For more information, Matt Drance is our Application Frameworks Evangelist. We've got a lot of documentation that talks about all of these principles, and I would really encourage you regardless of your skill level, to really take another look because a lot of these documents are long and they're pretty specific. And you go over them multiple times and you find something... that you may have missed the first time. So the iPhone Application Programming Guide talks about all of these fundamentals, and it's a real gem.
So please take the time to look at the guidance in there. The Memory Management Programming Guide for Cocoa talks about all the stuff that Alex had mentioned earlier, and most importantly with 3.0 just around the corner, and really it's basically here, go to the iPhone Dev Center and look at the iPhone OS 3.0 readiness checklist. Make sure you've got everything you need, everything's lined up for those submissions so that your apps in the store will be ready for iPhone OS 3.0.