Video hosted by Apple at devstreaming-cdn.apple.com

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-2013-15
$eventId
ID of event: tech-talks
$eventContentId
ID of session without event part: 2013-15
$eventShortId
Shortened ID of event: tech-talks
$year
Year of session: 2013
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2013] [Tech Talk 15] Architecti...

iOS 7 Tech Talks #15

Architecting Modern Apps, Part 2

2013 • 51:27

Learn the patterns that will help you quickly adopt new technologies and ensure that your app is taking full advantage of the latest devices and iOS capabilities. Dive into the details of moving to 64‑bit and see how your apps can tap into the Apple A7 processor.

Speaker: Dave DeLong

Unlisted on Apple Developer site

Transcript

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

In this session of architecting modern iOS apps, Part 2, we're going to be looking at components of new hardware, in particular the A7 chip. And then we're going to take a look at adopting the iOS 7 SDK, but in a way that maintains backwards compatibility with iOS 6.

Let's start off with building your apps for the A7. Now, of course, the big news about the A7 chip is that it's the world's first 64‑bit processor in a mobile device. Now, there was a lot of confusion when we announced the A7. A lot of people were wondering, well, why on earth would I need a 64‑bit chip in an iPhone? Am I really going to need to address more than 4 gigabytes of memory? Well, truth be told, there are a lot more benefits to 64‑bit than just a large address space.

This move to a new architecture has allowed us to make many changes. For example, we've doubled the number of integer and floating point registers available on the CPU. These registers are where the CPU performs its calculations. So having more registers means that it can spend more time performing calculations and less time moving data in and out of the CPU.

So ultimately, faster computation. We've also used this opportunity to adopt a new assembly language, one that is more efficient and up to the task of modern computation than previous assembly languages that we were using before. So again, a more efficient assembly code can mean faster performance. The 64‑bit architecture of the A7 also has allowed us to make optimizations to the Objective C runtime so that things like object allocation and even the ability to run the A7 in a single run can be done in a single run.

allocation, and even basic memory management, are now much faster. All of these together means that simply by recompiling your app for 64-bit means that you can see some pretty significant performance improvements. I also want to mention that the 64-bit standard that we have adopted on iOS is the same 64-bit standard that we're using on the Mac.

So by having the same standard for both 32-bit and 64-bit code, it means that all of the code that you write for your iOS apps is now easily portable to the Mac as well. This allows you to build apps that will last on both iOS and Mac. So that's a brief overview of some of the benefits of 64-bit. But let's take a look at how 64-bit will actually affect the app that you ship on the store.

If we crack open your application bundle, one of the very first things that we will find is your executable file. This is, of course, where all of your code has been compiled and will live, and it contains all of the instructions and all of your app's custom logic.

In addition to this file, you'll have many resource files, such as perhaps some nib files or storyboards or perhaps both. You'll have configuration files, maybe some JSON files, P list, databases, and so on. And, of course, you'll have image assets. So this is what your app looks like right now running on a 32‑bit device. But how does this change for a 64‑bit device? Watch closely.

[ Transcript missing ]

All you need to do to update your architecture's build setting is to find the build settings for your particular target and in particular find the architecture's line. Now, by default, this value will be standard architectures. And so ARMv7 and ARMv7S. All you need to do is change this to be standard architectures, including 64‑bit. By changing this value, you are indicating that when we compile your app for release build that we should be compiling it a version as well for 64‑bit. So, easy.

The second step is to update any third‑party libraries that you're using. All code that you run in a 64‑bit app must be compiled for 64‑bit. You cannot use a 32‑bit library inside a 64‑bit app, nor can you use a 64‑bit library inside a 32‑bit app. The architecture values must match. And so if you're using any third‑party libraries, such as, you know, connecting to somebody's service or whatever, then those must also be updated for 64‑bit.

[ Transcript missing ]

However, these changes to the data type sizes can cause some very subtle errors in our code. And also in the files that we create on disk and also in the memory that we use in an application. So first up, 64‑bit in our code. Now, one problem we might have is if we have code like this, where we're invoking a method that returns an NS integer and we're restoring the result of that method invocation into an int data type. Let's look at what's going on here. Well, an int, as we know, on both 32‑bit and 64‑bit devices is four bytes long.

But as we just learned, an NS integer on a 64-bit device is eight bytes long. And the last that I checked, you cannot put eight bytes of data inside four bytes. And so something has to give. And what gives are the top four bytes of this number. They're simply lost. We never get them, never can get that data back. So that data is gone. And if there was important information in those bytes, we now have corrupted data in our program.

So the easiest way to fix this is, of course, to make sure that our data types match. So the primitives that we're using to store the results of method invocations should match the return types of those method invocations. And similarly, the types that we're passing in parameters should match as well.

Dave DeLoong So we might be doing something like this, where we're copying a value from a long into a buffer. And here we've made the assumption that a long is four bytes long. And that would have been true on a 32‑bit device. But, of course, it's false on a 64‑bit device where longs are now eight bytes. And so we are only copying half of this number into our buffer. And so, of course, our buffer is going to end up containing garbage data. Dave DeLoong So the correct way to fix this is to use the size of operator.

which will return the correct size of this data type on that architecture. So on a 32-bit device, it would return 4, and on a 64-bit device for a long, it would return 8. By using the size of operator, we can make sure that we're correctly referring to the sizes of these primitives. Now, another way that 64-bit affects us in code is if we're trying to print a value into a string.

I expect this can be somewhat common. And so if we're using a format specifier like %d to print a long into a string, whether it's via printf or something higher level like NSString, then that would have worked on a 32-bit device because %d indicates copy 4 bytes of data into this string.

But of course, that will fail on a 64-bit device because 4 bytes is only half of the number. So what we need to instead do is make sure that our format specifier matches the data type and use, in this particular example, %ld, to indicate the long integer type. And then our string will contain the correct value. Now, for many of these, Xcode will warn you.

It will warn you that you are trying to, or that you have mismatched data types. It will warn you about format string specifiers not matching variable types and so on. So simply by setting your architecture's build setting to include the 64-bit architecture and recompiling your app, Xcode should be giving you several new warnings. Well, hopefully none if we've been doing this, correctly, all along. But it might give you some to go and fix.

But it won't give you all of them, which is why we are covering them today. And the underlying rule with all of these is to simply be consistent in our usage of data types. If we are asked for an 8-byte value, then we will give an 8-byte value. And if we're asked for a 4-byte value, we will give a 4-byte value.

We should be consistent in how we refer to the sizes of these primitives and not assume that we know the size, but simply ask, "What is the size of this system?" And it will give it to us. So that's how 64-bit will affect our app in code. What about on disk? Well, just as the problems as we saw with transferring data between stack frames could lead to issues of truncation of data, we can have that same issue with passing data between devices.

If I write out an 8-byte value on a 64-bit device and give it to another copy of my app but running on a 32-bit device, if I haven't written my app correctly, may end up only reading 4 bytes of data. Now, you may be thinking, "Well, this doesn't affect me. "Like, I don't have any sharing capabilities in my app "or, you know, I'm not connecting "to any sort of synchronization service or anything." And for the most part, you're right.

But we still need to be aware of this because in addition to the scenario of sharing data directly between devices, there's also the scenario of the user restoring their device from backup. What do I mean about this? Let's say one of your users has your app installed on her iPhone 3GS.

And she's been using your app, and your app has been writing files out to disk and has been saving them, and she has been performing backups to iCloud. What's been going on is that the system has been taking those files that you created on a 32-bit device and sending them up to iCloud.

But then we announce the iPhone 5S, and your customer has decided that she doesn't want her 3GS anymore. She's going to buy an iPhone 5S. So she does. And now to make the transition easy, she's going to restore her phone from backup. And what this means is that the files that you created and were saved to iCloud will now be copied down onto a 64-bit device. So the files that you create on a 32-bit device can still be opened on a 64-bit device, even if your app has no direct sharing capabilities.

So again, like we saw earlier, the key to fixing this and making sure that we don't run into problems is consistency. We'll read what we wrote. If we wrote a 32-bit value out to this file, then we should be reading in a 32-bit value. If we wrote out a 64-bit value, then we should be reading in a 64-bit value.

There are a couple of approaches that you might take to this. You could decide, for example, that your app will always only ever write 32‑bit values out to the file, regardless of what kind of device it's on. Or it could take the opposite approach of always deciding to write 64‑bit values, again, regardless of the device you're on.

The third approach is that you could be a bit more dynamic about it, where when you create a file, you would include some extra metadata to indicate that the values in this file are 32‑bit values, and so they should be read in as 32‑bit values. Or perhaps that the values are instead 64‑bit values, and therefore that you should read in 64‑bit values. These are all great approaches, and you should pick the best one that is appropriate for your scenario. So that's how 64‑bit affects your app on the disk. Finally, 64‑bit in memory.

Now, because pointers are twice as large as they used to be, you will use more memory. That is unavoidable. You cannot escape pointers. And you may think that to avoid this overhead of all of your pointers doubling in size that you will decide to leave your app as a 32‑bit app. And this sounds nice at first. But there's actually a very ‑‑ Interesting gotcha with this scenario.

And it has to do with how the operating system is implemented. So let's consider this gray box to represent how much memory we have available on an iOS device. Now, obviously, some of this memory is going to be taken up by the operating system itself in order for the device to run. And as part of the operating system's execution, it will be launching other applications, such as checking for email, connecting to iMessage, et cetera. So these all take up resources in RAM as well.

One of the other things that we load into this, into memory, is something that we call the shared frameworks cache. The shared frameworks cache is a single copy of the standard system frameworks. UI kit, foundation, core foundation, CF network, and so on. And the purpose of the shared frameworks cache is to provide a single location where any app that we launch can just be pointed to use that particular copy of the frameworks instead of just using the app itself.

So we have each copy of the app, or each app needing its own copy of the framework. But I want to point out that on a 64‑bit device, the shared frameworks cache by default is the 64‑bit copy of the frameworks. Which means that when we load a 32‑bit app into memory, we first have to load a second copy of the shared frameworks cache. A second copy of UI kit, a second copy of foundation, and so on.

Just for this one thing. So we load a 32‑bit app. And only after we load the second copy of the shared frameworks can we actually load this 32‑bit app into memory. Loading a second copy of the frameworks means that your app is much more likely to run into a scenario of constrained memory and getting a memory pressure warning. So how do we avoid this? The answer is simple, of course. Recompile your app for 64‑bit. And then don't incur the overhead.

: That's 64‑bit in memory. That covers a large part of the issues that will arise in 64‑bit, the ones that we feel will be the most common. Of course, the final step of adopting 64‑bit is to test all of these changes. By making these changes, you are, however slightly, altering the functionality of your application.

And so you should be testing these changes, both in the simulator and on a device, to make sure that your app still behaves as you expect it to. This is a great place to use the new continuous integration feature of Xcode 5 and OS X Server for Mavericks so that you can have your server run your unit tests and make sure that the changes that you have made are valid.

So there are a lot more nitty-gritty details to the 64-bit transition. There's a really fantastic guide in the documentation that I encourage you to read. It goes into some of the much more edge cases of the 64-bit transition, ones that we feel most people won't run into, such as having to use function pointers that take variadic arguments and so on. But we feel that this covers the majority of cases.

We also believe that this will be a relatively straightforward transition for you. Many of the developers that we've talked to so far who have performed this transition have reported back that this is a relatively painless process. One developer even reported that the whole thing took only about five minutes.

This is probably on the best possible case. Might take a little bit longer for you, maybe 15, no. Hopefully you've been following all of these patterns all along, just as you should have been. Now there's one other aspect of the new hardware that I wanna talk about, and that's the M7. The M7, as you're probably aware, is a brand new chip in the iPhone 5S that is dedicated to monitoring motion.

So we have taken the accelerometer, gyroscope, and compass sensors and embedded them in a single chip that we can keep running for very cheaply all the time. We can use this data to now infer things about how the user is moving. So for example, an iPhone 5S in my pocket would say that I am walking, right, 'cause I keep on walking back and forth, as opposed to standing still.

It's very easy to use the capabilities of the M7. You just keep on using the accelerometer and gyroscope and compass like you have been for the past couple of years. But there are a couple new features that you can take advantage of. Like I said, we can now track the user's motion, and for that you would use the CM Motion Activity Manager.

Through the Motion Activity Manager, you can request for updates on the user's motion, so you can be notified when they stop walking and are now standing still, when they start driving the car, or at least are in an automobile, when they're running, and so on. And you can also request historical data.

We keep up to seven days of historical data on the device. And we saw how this can be really awesome for a first launch experience. Like, if you've got a device that relies on this sort of data, you can, the very first time the user launches your app, request up to seven days' worth of data and immediately start presenting the user with useful and relevant information. The Motion Activity Manager is simple to use.

You simply allocate a new instance and ask it for updates. You provide a block, and it is through this block that we give you a motion activity object that contains information about the user's current motion. Now, the other thing that we've got thanks to the M7 is step counting. Like the Motion Activity Manager, it is also extremely simple to use. It's almost the exact same code, except instead of a Motion Activity Manager, it is called a step counter.

But again, you can start asking for updates, provide a block, and we will invoke this block as the user takes steps. So that was a brief look at the M7, and of course, the A7, and what it takes to adopt 64-bit for your app. Let's talk now about the M7.

We're now about building an app for iOS 7. We learned about some of the great new features of iOS 7, such as custom transitions, or UIKit dynamics, or TextKit. And we've heard in the past that many developers, they can adopt these new technologies while still maintaining backwards compatibility. We get this question a lot.

How do I use these features, but still support an older operating system? And many developers, of course none of you, but many developers are under the impression that you can't, that if you want to use new features, that you must only support that operating system. We saw this very significantly with the introduction of iOS 6 and UI Collection View, where many developers refused to use UI Collection View because they still had to support iOS 5.

In this session, in this part of the talk, we're going to learn how you can use all of these great new features, but still support iOS 6. It's totally possible. I promise you can do it. There are two parts to this answer. First, of course, you must use the iOS 7 base SDK. If you want iOS 7 features, you have to use the iOS 7 SDK. That's easy.

The second part of this answer is before you use a feature, check to see if it exists. If it does exist, of course, you're welcome to use it. If it does not, you shouldn't use it because it's not there. We feel that part of this misunderstanding about using new features comes from not understanding the relationship between the base SDK and the deployment target of an application. So we're going to review these really quickly. The base SDK is the foundation.

The technological foundation of your app. When your app runs on a device that corresponds to your base SDK, it should be the full feature experience, the best that you can possibly do. This value is actually encoded into your application binary. It's in the header of your executable that we saw earlier. And we encode the value that you use for your base SDK so that devices that run your app can intelligently choose how they behave. Amen.

If you think about it, an app that's compiled with the iOS 6 SDK is not expecting the iOS 7 look and feel when run on an iOS 7 device. And if you run an iOS 6 app on an iOS 7 device, that's, of course, what you see. You see it still looking and behaving like an iOS 6 app. And that's because the framework inspects this value that's encoded in the header, what value you used or what SDK you used to compile the app.

And it conditionally alters its behavior. It says, if this app was built for iOS 6 but not iOS 7, then I'm still going to take the old code paths and behave like an iOS 6 framework. Now, for your apps, this, of course, should be set, the base SDK should be set to use the latest iOS SDK so that you can take advantage of all of the great new features in the iOS 7 SDK.

But that does not mean you give up support for iOS 6. That's what your deployment target is. You should be setting your deployment target to iOS 6. As we saw this morning, 96% of iOS users are running on iOS 6 or iOS 7. It's not really worth your time to focus on the 4%. Dave DeLoong The way to look at this visually is we've got this history of iOS releases here.

If we wanted to build an app that supports iOS 6 and iOS 7, then we would set our base SDK to be iOS 7 and our deployment target to be iOS 6. They represent a minimum and maximum value. So that's just a quick reminder as to what the SDK and deployment target are.

Dave DeLoong Going back now to how do we adopt these new features? How do we check to see if a feature exists before we try to use it? Dave DeLoong Well, there are a couple of different kinds of features that you might check for. New frameworks and new classes, new methods, new device and system capabilities, whole lot of new designs, and of course, new device architectures.

First up, how do we adopt a new framework or a new class? In iOS 7, we have a brand‑new framework called Game Controller that allows your games to interact with external hardware, such as, you know, a game controller that contains a D‑pad, so that users can play your games with physical devices, with physical buttons.

Now, in order to use this, you, of course, would need to link against the Game Controller framework. And by default, the Game Controller framework will be a required framework, which means that your app will refuse to launch if Game Controller is not present. Now, that's not what we want, because we still want our app to launch on iOS 6, where Game Controller did not exist. So in order to do that, we need to go to our link binary with libraries build phase.

And change Game Controller from being a required framework to being an optional framework. By changing this framework to be optional, we are allowing our app to continue launching even if game controller is not present. So this is how we can adopt a new framework. We make the framework optional.

What about in code? What about all those game controller classes that we'll need to use? What happens to those? Here's a snippet of one way that we might use the game controller framework to start listening for wireless game controllers. Now, of course, this will work fine on iOS 7 because game controller exists.

But what about iOS 6? What happens with that GC controller class? When you specify that a framework is optional, what happens is when your app is loaded into memory, part of that loading process is that the system will go through and find all of these symbols, these class names and constants that you're using, and it will resolve them. It will either point them at the correct place to wherever they exist in whatever framework they're from, or if they're not there, they become nil.

And I hope you all know that it is totally safe to send messages to nil in Objective-C. We're not like other languages where we might crash your application. On iOS 6, this is no problem at all because this line of code simply does nothing. So this code, as is, without any modification, will work fine on iOS 6 and iOS 7. But this behavior of things becoming nil can have some unintended side effects.

For example, another part of the game controller framework is to listen for a particular notification. The GC controller did connect notification. This notification is posted, obviously, when a controller is actually connected to the device. Now, on iOS 7, this works. And as we saw on iOS 6, this GC controller did connect notification symbol would become nil. And that might sound nice, but this particular code has unintended side effects.

As it so happens, if you listen for a nil notification name coming from a nil object, you will actually be notified about every single notification for any name posted by any object. And that is a lot of notifications. This is probably not what you want. Dave DeLoong If we want to use this particular notification, we need to check to see if the symbol exists.

And we do that by checking to see if the address of the notification symbol itself is not nil. If the address is not zero or is not nil, then it is safe to use it. If it is nil, then obviously don't try to use it. Dave DeLoong So these are how we can adopt new classes and new frameworks.

We make new frameworks be optional. Dave DeLoong And for new class names or new classes, we make them optional. Dave DeLoong And for new symbols, we simply check to see if they're nil or not. Dave DeLoong Adopting new methods. So earlier, we learned about UI motion effect and how we can use UI motion effect to add that hint of depth to our user interface. We do that using this method on UIView, add motion effect.

This of course is a new method in iOS 7, works great. But on iOS 6 this will crash your app, because UIView knows nothing about the addMotionEffect method on iOS 6. Now we can't do a nil check here because of course UIView exists. It's just this method that doesn't.

So what we do here is we use a method that's on all NSObjects called respondsToSelector. This is a method where you pass in the name of another method, and it will return yes or no to indicate if that particular method exists. So if our view responds to the addMotionEffect selector, then of course it's safe to use. And if it doesn't, we shouldn't.

We like it when our apps do not crash. So this is how we can safely adopt new methods. Just check to see if they exist, using response to selector. There are times when we add new capabilities, both to the operating system and the hardware. We just learned about the CM Motion Activity Manager. And while this is a new class in iOS 7, it's really only useful on a device that contains the M7 chip.

And so if we wanted to use the Motion Activity Manager, checking to see if CM Motion Activity Manager is nil or not is an insufficient check, because it could be non-nil, but still not be able to track the user's motion. And so when we add new capabilities to the hardware or new capabilities to the software, we also provide a corresponding check to see if those features are available.

So we can check to see if we can track the user's motion. We can check to see if they can send email or if they're able to print, if they can monitor for certain kinds of location regions. We can even check to see if iCloud is enabled using the NS File Manager API.

In general, these methods usually return a Boolean, which means they're very easy to use as the condition to an if statement like this, where if this motion tracking activity is available, then of course it's safe to create a Motion Activity Manager. This is how we can safely adopt new capabilities while still maintaining backwards compatibility.

Now in iOS 7, we have adopted a brand new design language in our applications. And you may be wondering, what's the best way to check for that? That's not something that's really tied to a capability of the hardware, and it's not really something that's tied to any particular new class or new method.

And the way we've seen some developers try to figure this out in the past is doing something like taking the system version string and trying to parse it as a number and comparing it to see if it's larger than 6.1 or something. And that'll probably work. It's a little gross. But there's a much better way to do it. And that is using the NS Foundation version number constant in the Foundation framework.

This is a constant that's been in the framework since, well, Mac OS 10.0 and thus iOS 1, really. And for every major release of the operating system, pretty much, we've been updating this Foundation version number. And we've also been providing known or well-defined version numbers for what the particular version was of the framework for a particular release. And so you can use this Foundation version number to compare it to these well-known values, like this.

You get the Foundation version number, and you check to see, is it less than or equal to an iOS 6 style appearance for your app? If it's greater than this value, then you would default to an iOS 7 style appearance. So this is the safest and best way to check to see what version of the operating system you're on when that particular check is appropriate. Finally, adopting new architectures. So we learned about a few minutes ago. We have the brand new 64-bit chip in some devices.

And it's possible that you might want to write code that is specially optimized to only run on 64-bit devices. And the easiest way to do that is to authorize the system to affect how your app is compiled. We learned about these different slices of your app, where your code will be compiled one way for a 32-bit app and another way for a 64-bit app.

Using this compiler directive, this __LP64 directive, we can safely segment our code such that only portions of it will be compiled for 64-bit, and other portions would only be compiled for 32-bit. This is somewhat uncommon to use, but we are seeing some developers who are wanting to take advantage of the significant horsepower available via the A7 CPU and GPU to optimize for those particular scenarios. And this is the best way to do that. So those are the main ways that we would check for new features. But we're not done yet.

: We could put these if checks wherever we wanted in our code, but I want to talk now about how we can successfully encapsulate and hide these checks in a way such that only the people who really have to care about them know about them. Because if we were to scatter these checks everywhere throughout our code, we would quickly We've come to a state in our code base where our code is messy and unmaintainable and ugly. And code like that is just not fun to work on. So we're going to learn about three different ways that we can encapsulate these feature checks in a way that makes them easy to use and beautiful.

The first way is using a class cluster. If you're not familiar with this term, it's a pretty straightforward concept. A class cluster is a class that has a single interface that is not a single interface, but multiple implementations. Now, this isn't magic. It's really just using private subclasses. And this is actually a really common pattern in our frameworks.

Anytime you use almost any object from the foundation framework or many objects even from the UI kit framework, you are likely dealing with a class cluster. This allows us as framework implementers to optimize what version of the framework we're using. Dave DeLoong So the common example, of course, is for NSArray. If you have an array that is never going to be changed, we will give you one implementation of NSArray. But if you have one that will be changed and is thus mutable, we will give you a different implementation. But we even go beyond that.

We can look to see if you're going to be putting hundreds of thousands of objects into a mutable array, we might even give you a different implementation. One that is optimized for much larger data sets. And the beautiful thing about class clusters is that none of this is exposed to you. You don't have to worry about whether you're putting three objects into this array or three million. All of that is hidden from you.

Class clusters make this easy to do. Class clusters are a great way to encapsulate feature checks related to new classes, version checks, and capabilities, or even architectures. And as we'll see, it is extremely easy to remove obsolete code with class clusters. So let's go through an example. Let's say I'm writing an app that downloads something. Maybe it's levels for a game or it's movies for displaying to the user.

In order to perform these downloads, I'm going to create a download controller. And this is an object that I'm just simply going to say here is a download. I want you to begin it or pause it or resume it. And our other code will do just that. We'll interact with this download controller and say begin this, pause that, resume this, and so on.

We want to use a class cluster to implement our download controller. In iOS 7, we have this great new API with NSURL session for being able to perform downloads in the background. So even after the user stops using our app, we can still continue their downloads. We could not do this on iOS 6. We could kind of approximate downloads like that, of course, with NSURL connection.

And so we want to fall back to that API if the newer one is not working. So we're going to make download controller actually a class cluster. We will have two private subclasses, one for the new API and one for the old API. Dave DeLoong Now, the interface for our download controller is really simple. We have a convenience constructor for creating a new instance, and then we have just simple methods to start or pause or resume a download.

The implementation is where it gets interesting. As I said, we have these two private subclasses. One is a download controller that's specific to the NSURL session API. And then the other is a download controller that's specific to the NSURL connection API. Then when we're asked to create a download controller, we perform our feature check. We see does NSURL session exist? If it does, then I know that the new API is available to me. And so I will create the download controller that is able to use the new API.

If it doesn't exist, then of course I will default back to supporting the older API. I don't get as many features, but I can still perform downloads. That's the basic idea of a class cluster. Nobody who's using the download controller actually knows that this is going on. They just see download controller.

They don't see the specifics of how it's being performed. And maybe next year, we decide to drop support for iOS 6 in our app, which means we don't need the NSURL connection code anymore because NSURL session will always be available. In this case, it is trivial to remove the code that we don't need anymore. We simply delete a few.

We don't have to go wandering all around in our code base finding every single place where we were performing if NSURL session class does not equal nil, then use an NSURL session, else use an NSURL connection. We don't have to do that because we successfully encapsulated this feature check in a way that only one person or one object had to know about it. And so when we decide to make changes, we only have to make it in one place. Another pattern is data sources, or if you like the more technical term, this is the strategy pattern.

Whereas class clusters solve this problem through subclassing, data sources solve this problem through composition. We have a data source object that encapsulates the logic, and we can swap data sources in and out to get different logic. And again, like class clusters, this makes it very easy to encapsulate feature checks related to new capabilities, new classes.

And so let's decide now that we want our download controller to actually use this data source pattern. Well, again, we have the exact same interface for our download controller. It's the beauty of these patterns. We can change the implementations wholesale, and the interface never changes. So what would this look like in the M file? Well, instead of having two private subclasses, we would actually have a concrete download controller.

But it would have a property for another object that performs the downloads. In this case, I've made it conform to a protocol, a downloader protocol. You can imagine that this is a protocol that would define a start, a pause, a resume method, and so on. Then the download controller constructor would again perform our feature check. If the NSURL session class is available, then we will create a downloader data source that knows how to use the NSURL. So we can create a downloader data source that knows how to use the NSURL. session API.

If NSURL session is not available, we will create a data source that knows how to use the NSURL connection API. And then we will finish the allocation of an actual download controller by passing it a handle to this data source object, this strategy object. And then when we ask the download controller to pause or resume a download, all it really does is it turns around and asks its downloader data source how to actually do that. And that way the download controller doesn't have to worry about the specifics. It can leave those up to the downloader.

Like class clusters, when we no longer need our NSURL connection code, it is trivial to remove it. Again, we just delete a few lines, and now we've removed all traces of our NSURL connection code, and nobody is the wiser for it in the entire rest of our app. We only had to modify code in one place. So, class clusters and data sources, as I've been saying, make it very easy to encapsulate feature checks related to new classes, new capabilities, new designs, and new architectures. But they don't work very well for new methods.

For that, let's use a category. If you're unfamiliar with a category, the basic idea is that you can use categories to add new methods to classes that are not yours. So, for example, if you really wanted a new method on NSString that, you know, did something that's very specific to your application, you could add that method to NSString itself using a category. Categories are an excellent way to encapsulate feature checks related to new methods.

So, we saw this example earlier that trying to create a motion effect on iOS 6 and add it to a view would cause a crash because the add motion effect method doesn't exist. Now, we could put the response to selector check here. But we want to keep this code very linear and easy to read. So, instead of putting the check here, we're going to create a new method on UIView that will perform the check for us and then forward on to the next method. So, we're going to create this category to define a new method called TT underscore add motion effect.

We've named this method such that it's obvious what its purpose is. And we've given it a prefix to make sure that we're never going to interfere with another method on UIView. The implementation of this method is extremely simple. Here in this method, we simply check to see does the view, or in this case self, respond to the add motion effect selector.

And of course, if it does, then it is safe to invoke add motion effect. And if it doesn't, then we'll just drop the message on the floor and not do anything. Now, everywhere else in our app, all we have to do is use tt_add_motion_effect. And then we can safely construct motion effects, which may or may not return nil, and pass them to our new add motion effect method without any worry of our app crashing. And we have kept our app running. And we have kept all the rest of our code in our app simple and easy to read and understandable.

Now you may be thinking, well, I've still scattered this all over my app. Once I drop support for iOS 6, is it difficult to remove this category when I don't need it anymore? No, it's actually quite easy. Xcode has some refactoring tools. And so the way that you would remove this category when you don't need it anymore is to simply rename this category method to the actual method name in UIKit.

What this will do is it will go through your entire code base and find everywhere where you're using this custom method and replace it with the actual selector from UIKit. Once this is done and it takes about three seconds, you're safe to delete your category because you don't need it anymore.

This is how you can safely adopt new methods while still maintaining backwards compatibility with iOS 6. I want to end by talking about the concept of backporting features. As I talked about earlier, there were some developers who were resistant to adopting UI collection view because their justification was that it does not exist on iOS 5.

That's okay. For many of you, your app already worked fine on iOS 5, or for this year, it already works great on iOS 6. You can still adopt these features, but just make them conditional to only run on iOS 7. What then would you do on iOS 6? Just leave your app as it is. Some features are simple enough to approximate on older devices. As we saw with downloads, we can perform backporting.

We can do background downloads on iOS 6, but we can get pretty close with NSURL connection. That's a feature that's easy to approximate on an older iOS device. And so it's probably worth our time to invest in that development cost. But if you wanted to use game controllers on iOS 6, you would have to rewrite that entire framework yourself. I don't recommend that you do that. That would be a lot of work.

So in those cases, it's all right to scale back the experience of your application if certain features are not available. We see game developers do this. For example, when we had the introduction of the iPad Air, one of the things that we showed was Infinity Blade 3. And the Infinity Blade developers talked about how for the new device, they were able to ramp up the effects in Infinity Blade. But they only did that for the new device, where the capabilities were present. They didn't try to put all of that texture in detail on the dragon on an iPhone 4, because an iPhone 4 can't handle that. It does not have the capabilities.

We can learn from their wisdom. Just as they apply this rule to the graphics of their game, we can apply this rule to the features of our app as well. Let's make the best possible app that we can for the latest possible system. And for older versions of the operating system, We can leave our app as is, maybe backport some things if it's worth our time.

So that is a look at how we can adopt new features of the iOS 7 SDK while still maintaining backwards compatibility. I want to thank you all for coming today. Remind you that the labs are still open. You're welcome to come and ask us all the questions you have. I hope you have a great rest of the afternoon. Thank you.