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-23
$eventId
ID of event: tech-talks
$eventContentId
ID of session without event part: 2013-23
$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 23] Architecti...

iOS 7 Tech Talks #23

Architecting Modern iOS Games

2013 • 58:45

Learn the patterns that can help you quickly adopt new technologies and keep your games on the cutting edge. Understand how to use powerful constructs from UIKit to make your game feel at home on iOS. Dive into the details of moving to 64‑bit and see how your games 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.

Welcome to architecting modern iOS games. My name is Dave DeLoong and I'm one of the app frameworks evangelists. In the previous session, we heard a lot about the A7 GPU and its capabilities. In this session, we're going to be looking at the CPU portion of that chip and looking at what this transition to a 64‑bit processor means for you and your games.

We're also going to be taking a look at the iOS 7 SDK, not at any single feature in particular, but in general, how you can adopt new features available in iOS 7 while still maintaining backwards compatibility with iOS 6. But first, let's start with building your games for the A7.

When we announced the A7 chip, one of the things that people were most surprised to learn about this chip is that it's the world's first 64‑bit processor in a mobile device. And when we made this announcement, there was a lot of confusion about why we would make a 64‑bit chip in a device like an iPhone.

[ Transcript missing ]

All of these together mean that recompiling your games for 64‑bit can give you a faster experience in your game. You will get better performance by recompiling for 64‑bit. I also want to point out that the standard that we used for 64‑bit on iOS is the same standard, the LP64 standard that we use on the Mac.

And so by having this common standard between iOS and Mac, it makes it much easier to port your games from iOS to Mac as well. Having this shared standard means that we have a solid foundation on which we can build modern games that will last for many years. So that's just a brief overview of some of the 64‑bit benefits. Let's see how 64‑bit actually affects your applications bundle.

When we open up an iOS app or a game and take a look inside the bundle, one of the very first things that we'll find is the application executable. This is the file where all of your code is compiled. This is what gets launched. But in addition to the executable file, there are many other files.

Perhaps you might have some interface builder files such as NIMS or storyboards. You'll probably have a bunch of configuration files, whether they're P lists or JSON files or something else. And of course, you will have lots of image assets. This is how all of our apps look today on a 32‑bit device. So how does this change for 64‑bit? It's very simple. Just like this.

Nothing changes whatsoever. That was a trick question to see if you were paying attention. From this perspective, nothing is different. To adopt 64‑bit, you do not need to alter any of your image resources, none of your configuration files will change, none of your interface will change at all. The only thing that's affected by the 64‑bit transition is the executable file itself. Let's take a look inside this file and see what happens.

When we open up this file, the very first thing at the very beginning of this file is something that's called the header. Now, the header to this file describes the contents of this file. And it will describe what code is contained in this file, what devices it has been compiled for and so on. And so, for example, in a standard iOS app, this header would describe a segment or slice of this file that contains code compiled using the ARM view. And this is code that would run on an older iOS device, such as an iPhone 3GS.

The header might also describe a slice of this file that's been compiled using the ARMV7S instruction set. This is code that would run on a more recent iOS device, such as an iPhone 5. And so to make an app a 64‑bit app, all we need to do is add one more slice to this file using the new instruction set, the ARM64 instruction set. The presence of this portion of the executable file is what makes an app a 64‑bit app.

Now, because we are adding more code into this file, the app will be larger in size. But remember that these changes are limited to only the executable file itself. The main bulk of an app size comes from its assets and its other resources. So on the whole, this shouldn't be a very large change to the application bundle size. And by having multiple architectures in a single file like this, it means that you only need to ship a single application to support every iOS device.

So let's go through the steps of adopting 64‑bit in your games. The very first step is to update the architecture's build setting. This is very trivial. All we need to do to do this is to open up Xcode and go to the build settings for our target and find the architecture's build setting. By default, on your existing projects, this will likely be standard architectures. All you need to do to make your app compile for 64‑bit is to change this value to standard architectures including 64‑bit. That's all there is to it. It takes about 10 seconds.

The next thing you'll need to do is update any third‑party libraries that you may be using. In a 64‑bit app, all the code that executes must also be compiled for 64‑bit. So if you're using a third‑party library, whether it's a game engine or perhaps a library to link to some synchronization service or anything like that, that code must also be compiled for 64‑bit if you want to ship a 64‑bit app. You cannot use a 32‑bit library in a 64‑bit app or vice versa.

Going back to this model of the contents of our executable file, if one of our libraries were not built for 64 bits, it would be like part of this file would be missing. And in fact, we wouldn't even be able to compile our applications. So make sure that you update your third‑party libraries. Now, there's a very easy way to check to see if the libraries you already have support 64 bits or not.

To do this, we just open up terminal and use the file command. We pass in the name of the .A file. Now, the file command will inspect our executables and it will list the contents found in that header. And so, looking at this library here, we can see that this is a library that contains five architectures, five slices, three for iOS devices and two for the simulators. And what we're looking for in particular is the presence of the ARM64 slice. If your libraries have this line, then you're ready to go.

The bulk of your time in moving to 64‑bit will likely be spent fixing any of the small issues that arise from the new 64‑bit standard. When we talk about the 32‑ and 64‑bit standards, they center around how we deal with primitives. So this is what we're used to seeing.

This is on a 32‑bit device where chars are a single byte, shorts are two bytes, pointers are four bytes, longs are also four bytes. That should be very familiar to us all. This is known as the ILP32 standard. So integers, longs, and pointers are 32 bits. Now, the standard that we've adopted for 64 bits is known as the LP64 standard. So what changes then are longs and pointers and any type derived from those. So on a 64‑bit device now, longs are 64 bits and pointers are also 64 bits long.

This has some advantages. When we refer to data types such as NS integer, we go from being able to represent about 4 billion possible values on a 32‑bit device to being able to represent over 18 quintillion values in a single NS integer on a 64‑bit device. CG float and other doubles similarly get this massive increase in potential range.

Dave DeLoong: So, for example, in your code, you might be doing something like this, where you're invoking a method that has an NS integer return type, and you're taking the value that's returned from this method and storing it into an int data type. Now, as we know, an int is four bytes long.

But on a 64‑bit device, an NS integer is now 8 bytes long. And last I checked, you can't put 8 bytes of data inside 4 bytes. It would revolutionize compression if we could. So what happens then is that we lose half of our data. The first 4 bytes of our NS integer just disappear.

And if those bytes contained important information, we now have corrupt data. We have truncated this number, we've lost half of it, and our data is now invalid. So how do we fix this? Very easily. We just make sure that our data types match. If our method returns an 8‑byte value, we should be putting it into an 8‑byte data type. If it returns a 4‑byte value, we put it into a 4‑byte data type, and so on.

Similarly, we might be trying to copy some data out of a primitive and into a buffer. Now, here we've made the assumption that our longs are going to be four bytes long. And that would have been true, of course, on a 32‑bit device. But on a 64‑bit device, of course, longs are now eight bytes. And so, again, if we only copy four of those bytes, we'll have lost half of our number. And our buffer will end up containing garbage.

So instead of assuming that we know the size of our data types, let's be a bit more flexible about it. And instead, we'll use the size of operator so that we can dynamically refer to the size of these data types and not base our code on potentially faulty assumptions.

Another way that 64‑bit can affect our code is if we're trying to print a variable, a data type, into a string. doesn't have to be NSString. This even affects things as low level as printf. So here we're trying to print a long into a string and we're using the %d format specifier. Now, the %d format specifier says take a 4‑byte value and treat it as a signed integer.

Of course, as we've just learned, longs are now 8 bytes. And so if we were to try and print an 8‑byte long using the %d modifier into the string, we would end up printing only four of those bytes and would end up with garbage in our string. So again, let's use the correct format specifier, %ld, for a long integer so that our string will contain the correct value.

There's a single rule to deal with all of these, and it's simply the idea of consistency. Let's be consistent in how we refer to data types. If our methods return an 8‑byte value, let's store it into an 8‑byte variable. Let's make sure that we're not making assumptions about the sizes of these data types either. Let's use the size of operator instead. Finally, let's make sure that we're consistent between the format strings that we use and the variables that we're trying to print into them.

So that's how 64‑bit will affect our code in our games. It will also affect the data that we write to disk. If we consider sharing data between devices, many of your users will have 32‑bit devices, but many of them will have an iPhone 5S or an iPad Air and thus be running on 64‑bit devices. And so we're going to be using 64‑bit devices.

So if you do any peer‑to‑peer communication or even any communication between the device and a server, you need to make sure that you're accurately representing the data that is going between these devices. If you take an NS integer, for example, and try to send it from a 64‑bit device to a 32‑bit device where it's also treated as an NS integer, then you will deal with these exact same problems of truncation that we just talked about.

You may be thinking, well, this doesn't affect me. My game has no server communication and it doesn't do any sort of peer‑to‑peer or local game play, so I don't have to worry about this. Well, that's not quite true. Because even if your game is only writing files to disk, you still might run into this problem when you consider the scenario of restoring a device from backup.

Let's consider one of your users. This user has an iPhone 4. It's a couple of years old. They've been very happy with this device. They've been running your games on it. Your games have been writing data out to disk. This user has been backing this device up to iCloud. This iPhone has been taking those files that you've been creating on a 32‑bit device and saving them to iCloud.

Dave DeLoong: Then we announced the iPhone 5S. Now this customer has decided they want to get rid of their iPhone 4, buy an iPhone 5S. So they do this. They get a very nice white one. And to make the transition easy, they decide to restore this device from their iCloud backup. This is a very common scenario.

And so in the process of restoration, that same file that you created on a 32‑bit device is now copied down from iCloud onto the 64‑bit device. And so again, we are dealing with the scenario of we have data created on a 32‑bit device, but it's being read on a 64‑bit device.

And so we need to be flexible in how we interpret this data. Going back to the idea of consistency, we need to read exactly what we have written from this file. If we have written a 32‑bit value, we should read a 32‑bit value out of this file. And if we have written a 64‑bit value to this file, we need to read out a value.

There are a couple of ways that we can approach this problem. We can establish the policy that we will always, only ever write 32-bit values to this file, regardless of whether it's a 32- or 64-bit device. Or we could take the opposite approach, that on every device, we will always be writing 64-bit values. So long longs, doubles, and so on. And we will always be reading 64-bit values, again regardless of what kind of device we're on.

A third approach that you could take is to, again, be more dynamic about it. In addition to whatever data you write to this file, you could also include some metadata that, perhaps, this file contains 32-bit values. And then as you read in this file on either a 32- or a 64-bit device, that you correctly interpret this file by looking at this metadata and saying, "Aha, it contains 32-bit values.

I will read 32-bit values." Or perhaps it contains 64-bit values. And you, again, you read what you write. And when you read what you write, you avoid those same problems of truncation that arose earlier in your code. So this is how 64-bit will affect data that you write to disk.

64‑bit will also affect the memory usage of your games. Now, because pointers have doubled in size, you will end up using more memory. That is unavoidable. You all use pointers. There's no escaping pointers, whether for good or for evil. You cannot escape them. And you may think that to avoid the penalty of doubling the size of all of those pointers, that you're just going to keep your app as a 32‑bit app. And that sounds really nice. Except there's a problem. And the problem arises from what gets loaded into memory when a 64‑bit device tries to launch a 32‑bit application.

So let's consider the amount of memory that we have on an iOS device. This gray box will represent our RAM. Now, loaded into memory, of course, is the operating system itself. The device has to run. And the operating system will also load many helper applications so that it can connect to iCloud and download your email or connect to iMessage or any number of other services. So system applications take up memory as well.

One of the other things we load into memory is something we call the shared frameworks cache. The shared frameworks cache is a single copy of the standard application frameworks. So UIKit, foundation, core foundation, CF network, so on. They're packaged up as a single binary and when the device launches and starts up, we load this cache into memory such that when we launch other applications, instead of having to give them their own copy of UIKit, we load it into memory. We can simply point them at the shared cache and say, there's your copy over there. Use that one.

I want to emphasize on a 64‑bit device, the shared frameworks cache that gets loaded is of course the 64‑bit version of the system frameworks. As we learned a few minutes ago, a 32‑bit app cannot use 64‑bit libraries. So when we launch a 32‑bit application on a 64‑bit device, we have to load a second copy of the system frameworks. And only after we load a second copy of the frameworks can we actually load a 32‑bit app into memory.

Loading a second copy of the frameworks means that you are now much more likely to run into a situation where the device is running out of memory. This is not what we want to have happen. Because when we run out of memory, apps start getting killed. And eventually, if things get bad enough, your app might get killed as well. So let's avoid this problem. And the way that we avoid it is, of course, by compiling our apps for 64‑bit and therefore not incurring the penalty of loading the 32‑bit copy of the frameworks.

So that's one of the ways that 64‑bit can affect our app's memory usage. But 64‑bit can also affect our app's memory usage in another way, and that has to do with the alignment of primitives. So this is a very similar table to the one we saw earlier, but this instead refers to where data starts in memory.

So on a 32‑bit device, you could have a char start at every single byte offset in memory. But for things like longs or pointers, they naturally only start every four bytes in memory. So, for example, at offset zero or offset three or offset seven. And it would be very unnatural for them to start at offset one.

On a 64‑bit device, these alignments change slightly. So, again, longs and pointers, instead of being aligned on a 4‑byte boundary, are now aligned on an 8‑byte boundary. Now, this might be not very easy to intuit what this means. So let's take an example of dealing with a custom struct.

Many of you probably have structs in your games to efficiently represent data that you're passing around. We've got a simple struct here that contains two chars, an int, and a pointer. On a 32‑bit device with this layout, These 10 bytes of data actually take up 16 bytes of memory.

Because our first char can be aligned on a one‑byte boundary, but our int has to be aligned on a four‑byte boundary. So in between our char and our int, there are three bytes of padding. And again, after the second char and the pointer, there are again three bytes of padding. So already on a 32‑bit device, we've got six wasted bytes of space. On a 64-bit device, a couple of things happen.

First off, of course, as we know, our pointer will double in size because pointers are now 8 bytes long. But also, pointers are now aligned on an 8‑byte boundary. So what this means is that instead of having 3 bytes of padding after that second char member, we now have 7 bytes.

So this struct, which now takes up 14 bytes of data or only contains 14 bytes of data, is actually taking up 24 bytes of memory. So each instance of this struct has 10 wasted bytes of space. Now, there are two approaches that we could take to solving this. The first one is called packing. And what packing does is this indicates to the compiler, do not insert those extra bytes of padding. adding. So that our struct representation in memory would look like this. But spoiler alert, you don't want to do this.

And you don't want to do this because the CPU still has to align this data in order to read it. So, for example, if the CPU wanted to read the pointer out of this struct, it would still have to copy this struct's contents into a new block of memory, realign it so that things are aligned on their natural boundaries, and then it could read the pointer data out of the struct.

That's a lot of extra work that the CPU should not have to do. So you should really avoid packing structs like this if you at all can. So this is the first way you can solve this problem, and it's probably not the best way. So the other way that you can do it is instead by rearranging the members of your struct like this.

By rearranging the members of the struct, we can efficiently pack the data in our structs in a natural way. And the secret here is to simply order the members of our struct by size. Order them from largest to smallest. And by ordering them from largest to smallest, the members of the struct will naturally align to their proper boundaries with almost zero wasted space. And in many cases, with no wasted space at all.

And so on a 64‑bit device, as we're dealing with these 14 bytes of data, in a properly organized struct, it will take up exactly 14 bytes of data. On a 32‑bit device, it would take up only the requisite 10 bytes. So this is another way that 64‑bit can affect your app's memory usage. If you're dealing with custom structs, make sure that you've organized them properly so that you are using your memory as efficiently as possible.

So that's a quick overview of some of the issues that might arise as you adopt 64‑bit in your games. Now the final thing that you'll want to do is of course to test these changes. By making these changes to the data types or perhaps even to the organization of the structs in your games, you are necessarily altering the behavior of your game. And it behooves you to test these changes to make sure that they do exactly what you intend to, especially when it comes to reordering those structs.

If you have structs that have been simply serialized directly to a byte stream and then saved to disk, you need to make sure that you correctly read those back in in the old order before transforming them into their new more efficient organization. This is where things like continuous integration and unit testing can make it really easy to verify that the changes that you have made to your games are correct and do exactly what you want.

So that's a summary of 64‑bit on the A7 and what it means for your games. We have a really excellent piece of documentation called the 64‑bit transition guide for Cocoa Touch. It goes into a lot more detail than I just did on all of the changes that 64‑bit means for iOS applications. And it even deals with some of the more esoteric corner cases, such as how you properly deal with function pointers that take variadic arguments, which we figured that most people wouldn't. wouldn't need to know about, but it's still important.

Now, this might sound like a lot of work, but if you've been doing everything that you're supposed to have been doing, if you haven't been making assumptions about the sizes of data types, and if you've been consistent in how you refer to data, then this transition should actually be extremely straightforward.

As we've worked with many of you and other developers in adopting 64‑bit, overwhelmingly, the reaction has been that this is pretty easy. We've even heard some stories of developers being able to do this full transition in as little as five minutes, although that is kind of an ideal case. It will probably take you a little bit more, but hopefully not too long.

So that was a quick look at the A7 and how you can take advantage of 64‑bit in your games. Let's move on now to building your games for iOS 7. Whenever we introduce a new SDK, one of the big questions that many developers have is how they can use all of the great new features of this new operating system but still support older versions.

Literally this question. How do I use iOS 7 features but still support iOS 6? And there's a lot of misinformation out there about how you actually do this. And many developers, but I'm sure none of you, are under the assumption that you can't. That if you want to support iOS 6, that you still must compile with the iOS 6 SDK. And if you wanted to use things like game controllers or the new background download API, that you're just kind of out of luck. Or that it would be way too hard to try and bifurcate your app's logic so that it can handle both cases.

Well, in this part of the talk, we're going to learn that it's possible and it, in fact, is quite easy to do this. So let's answer this question. How do we support iOS 7 while still maintaining backwards compatibility with iOS 6? There are two parts to this answer. And the first part is, of course, we must use the iOS 7 base SDK. If you want iOS 7 features, got to use the iOS 7 SDK. That's easy.

The second part of this answer is in your code, before you want to use new API like perhaps game controllers or these background downloads that I've been talking about, you first want to check to see if they're actually present. If they are, great. If they're not, well, then, of course, don't try to use them.

So we've identified five main categories of features that we introduce with each new SDK. They center around new frameworks and therefore new classes, new methods on existing classes, new capabilities to hardware and software, new designs, and as we've just learned about, new architectures. So how do we adopt a new framework? In iOS 7, we've added a couple of new frameworks like SpriteKit and Game Controller. I imagine many of you would be interested in adding support for Game Controllers to your applications. You'll hear a lot more about how you actually do that later this afternoon.

The summary, though, is of course that we're going to end up linking the game controller framework into our target. And when we add the game controller framework to our link binary with libraries build phase, by default, this is going to be a required framework. And what that means is that our application would refuse to launch if the framework is not present on the hosting device because it's required.

So in this case, by requiring game controller, we have effectively set our deployment target to iOS 7. Regardless of what the actual setting is in our build settings, if we require game controller, our app will only ever run on iOS 7 because it would refuse to launch on iOS 6. That's not what we want.

So what do we do? Well, we simply click that little required pop up over on the right. change the framework to be optional. Now, by making this framework be optional, we are indicating that our app is able to handle the game controller framework not being present. And so if it's not there, we should still continue to launch anyway.

Now, some of you perhaps are not using Xcode to compile your games. And even if that's the case, you can still take advantage of these optional frameworks. Instead of using the dash framework flag to link in the framework at the linking phase, you would simply use the dash week underscore framework flag.

And that's how you indicate that a manually compiled framework or manually compiled app should weakly link against the game controller framework. But what does this mean for your code? Here's a line of code we would probably be using if we were trying to use the game controller framework and we want to support wireless game controllers.

On iOS 7, this of course will work great. But what about on iOS 6? Well, when your application starts up, one of the things that we do is called symbol resolution. And as your application loads, we're going through and we're seeing, okay, you're using the UI application class.

That's over there. You're using UI view. That's also over there. You're using NSString. Well, that one's over there. And we go through and we match up the symbols indicated in your executable with where they're actually living at in memory in that shared frameworks cache. But what about when the symbol doesn't exist? Well, since we indicated that the game controller framework was optional, we just simply make that symbol be nothing. It's nil.

And I hope you all know that in Objective C, it is totally safe to send messages to nil. We will not throw an exception. Your app will not crash. This code works great. In fact, it just does nothing. So if we want to use that line of game controller code, then we can just use it as is with no modifications on both iOS 6 and iOS 7. Just like that.

But there are times when this conversion to nil can have some unintended side effects. So for example, here's another part of the game controller framework. We need to listen to a notification for when we've actually connected to a controller. This is how we would do that on iOS 7 by registering with NS Notification Center under that appropriate notification name.

But as we just learned, on iOS 6 when our app gets loaded, that GC controller did connect notification symbol resolves to nil, which means on iOS 6, our code becomes this. Where we're registering for a nil notification name coming from a nil object. Now, that might not sound like a bad idea, but it actually has some unintended side effects. And what happens is when you register for notification from NS Notification Center for any notification, so therefore a nil notification name coming from a nil object, we will notify you about every single notification.

Any notification name coming from any object. That's what that means. What it means in practice is that as your app starts up and as your app continues to run, we will spam your observer with thousands of notifications. So this is probably not what you want to have happen.

It's a bit overkill. So instead, we need to check to see if the feature we want to use exists. We do that by checking to see if the symbol itself is non‑nil. We do that simply by taking the address of the notification name constant and checking it against null. If it's non‑null, then we know that the symbol exists and we can safely use it as the name of our NS notification.

So this side effect of converting symbols to nil is a great way to check if frameworks are present or if new classes exist. If you're using a new class, whether it's in a well‑established framework like UIKit or a brand new framework like SpriteKit, if that class does not exist on that iOS device, it will also become nil and you can easily test for that as well. So this is how we adopt new frameworks and new classes. Thank you. Thank you. Thank you. Thank you. Thank you.

On iOS 7, we've added some API called UI Motion Effect that allows us to add that parallax effect that you see on the iOS 7 home screen. It's really easy to add this to your own views. We're not going to go into exactly how you want to do that.

If you want to know that, you can check out some videos from WWDC or perhaps some of the videos from the Apps Day tech talks. Regardless, the way that you add a motion effect to a particular view is using this new method on UI View called Add Motion Effect.

Now, UIView is obviously a class that has been around for a long time, so we can't check to see if the UIView class is nil. And so we can't check to see if the add motion effect method is nil either. So maybe we just try to execute this code. Well, on iOS 7 that would work, but on iOS 6 we would crash. Not the best user experience. So instead, we need to check to see if the method itself exists. And we do that using a special method on every NS object called responds to selector.

Response to selector is a great way that you can check to see if any method at all exists on a particular object. So here we're going to check to see if our UI view has a method called add motion effect. If it does, then it's safe to call it. And if it does not, we obviously shouldn't. So using the response to selector check is the best way to adopt new methods while still maintaining backwards compatibility with older operating systems where those methods may not be present.

So adopting new capabilities. What do I mean by a capability? A capability is something that is not necessarily tied to any particular version of the operating system itself. So for example, with the new M7 motion coprocessor in the iPhone 5S, You have the ability to know whether the user is, for example, walking or standing still or running or perhaps even in an automobile. You can even count their steps if you like.

But this is a capability that is only available on the iPhone 5S. It is not a feature for iOS 7 in general. And so if you were riding a game that took advantage of this ability to track the user's motion, then you would need to check if this capability exists.

Because if you tried to use motion tracking on an iPhone 4, it wouldn't do anything, even though it's running iOS 7. And so when we add new capabilities like this, we also add, via API, a way to check to see if this capability is present. So for motion tracking, it's the isActivityAvailable class method on CM Motion Activity Manager.

There are all sorts of corresponding checks for various other system capabilities such as whether or not you're able to send email or whether or not certain kinds of regions are available for monitoring. You can even check to see if you can print or if iCloud is enabled with the NS file manager API.

Most of these methods simply return a convenient Boolean and so they're very easy to use as the condition for an if statement. So in this example, if we wanted to use motion tracking in our games, we simply check is motion tracking available and if it is, then let's create our motion activity manager. If it's not, don't try to use it. So this is how we can safely adopt new capabilities while still maintaining backwards compatibility with iOS 6.

Next up, new designs. In iOS 7, we have added a brand‑new design language to the operating system, emphasizing the user's content over everything else. And you may have decided that in your games, you want to have a different look on iOS 7 than you do on iOS 6.

So what's the best way to check to see which look you should use? We've seen some developers do things such as take the system version string and try to interpret it. And then compare it to something like 6.1. But there's a better way to check the operating system version number. And that's using a constant in the foundation framework called the NS foundation version number.

[ Transcript missing ]

Finally, adopting new architectures. Some of you might want to take extra advantage of the A7 CPU and GPU and perhaps hand optimize some assembly code or simply guard some code that's only supposed to run on a 64‑bit processor. The easiest way to do that is by actually effecting the compilation of your games.

We learned earlier about how our code is divided up into slices in our executable file. By using these sorts of compiler directors, we can indicate which slice we want our code to end up in. And so by using this pound if underscore underscore LP64 directive, we can indicate that we have code that should only end up in the 64‑bit version of our executable.

And thus will only ever run on a 64‑bit device. Of course, conversely, we can indicate that some code can only run on a 32‑bit device. This is not perhaps the most common thing to want to do, but if this is what you are doing in your application, this is the best way to isolate code to only run on a 64‑bit device.

So these are the ways that we check for new features. We can check to see if symbols are nil. We can check to see if certain methods exist. We can look at the capabilities APIs to see if things like printing or motion tracking are available. We can check the foundation version number to see what version of the operating system we're on. And we can also use compiler directives to indicate that some code should only work on a 64-bit device or only on a 32-bit device. So there are lots of different kinds of checks that we can use. But I now want to talk about hiding these checks.

Because we could just put these checks anywhere in our code. But if we were to do that, just scatter these checks everywhere, we would quickly end up with code that is kind of messy. That as we're reading it, we've just got branches that go everywhere of, oh, if I'm on a 64-bit device, do this.

If I'm on a 32-bit device, do that. But on 64-bit, check to see if motion tracking is available and if it is. Just all of these permutations that would infest our code. And code that has logic like that is not fun to work on. It is complicated and messy, and it is difficult to understand.

So let's talk now about how we can successfully encapsulate these feature checks and hide them from all other parts of our application so that only the parts of our app that have to know about it actually do. The first pattern I want to talk about is called class clusters. If you haven't heard this term before, it's pretty easy to understand. The idea behind a class cluster is that we have a class that has a single interface but multiple implementations.

This is not some heretofore unknown behavior of Objective C. It's really just private subclasses. We call this pattern class clusters. And you've all used class clusters. Anytime you've used an NSString or an NSArray or even a UIButton, you've been using a class cluster. And what we do is we look at how you're using this object.

And we change the implementation of this object or perhaps just give you a different implementation of this object depending on how you're going to use it. So for example, probably most of you didn't know that there were subclasses of NSArray that are optimized for holding enormous amounts of data.

It's all just an NS mutable array to you. But under the hood, we've got a myriad of different implementations of NSArray that are optimized for different scenarios. That's the beauty of a class cluster. You get a single interface that's easy to understand, but many different implementations optimized for your use case. So let's take a look at an example of how you can use class clusters to hide feature checks.

I've been alluding to this new background download API in iOS 7. Let's say we're writing a game where we want to use this new background download API and if it's not present, then we'll default to our older network communication stack, perhaps NSURL connection. We're going to create a download controller object and this will be an object that just performs downloads. We say download this thing, pause that, and so on. And the other, everything else in our code is simply going to interact with our download controller, sending messages back and forth. Simply pause this, start that, resume this other thing, what's the progress on that, and so on.

[ Transcript missing ]

It's very easy to understand. We've got a constructor function for making a new instance and then just some methods to start and pause and so on. If we look at the implementation now, we actually really-- here's the magic-- have two private subclasses. One of these subclasses will be specific to the NSURLSession API. That's the new background download API. And then the other subclass will be specific to our older logic, using NSURLConnection.

So that we could perform downloads simply there in the process itself. And then when we're asked to create a new instance of the download controller, we will check, is the feature we want to use available? Does NSURLSession exist? If it does, we'll actually allocate the subclass that's specific to NSURLSession. If it doesn't exist, then we'll default to the older API and give you the download controller that can perform downloads using the older NSURLConnection code.

That's all there is to it. Now-- So we can perform downloads all across our games and not care how they're actually being downloaded. Could be being downloaded in process. It could be being downloaded out of process. We don't care. We have successfully abstracted this check to only a single part of our application.

And what this means is that when we decide to drop support for iOS 6, we don't need our NSURLConnection code anymore. So instead of having to dig across-- every part of our application and find every place where we were ever using an NSURLConnection, instead, we simply delete a few lines of code and we're done.

Dave DeLoong: That's how class clusters can help hide checks related to new frameworks and classes, new capabilities, even new architectures and designs. Now, another approach that we could take is to use a data source. So class clusters solve this by using subclasses, and data sources solve this by using composition.

A data source object would then be another object that encapsulates all of the peculiarities of a particular API and then you simply interact with this wrapper, so to speak. So again, now let's say that we want our download controller to instead of being a class cluster, use a data source to perform downloads.

Again, we have the exact same interface for our download controller. That's the beauty of these patterns is that we can change the implementation wholesale and only one part of our application has to change. So here's our download controller interface, the exact same thing it was before. I literally just copied the slide. But our implementation is a bit different. Instead of having subclasses, now our download controller has a property to another object.

In this case, I've made it conform to a TT downloader protocol. This would be a protocol that simply defines a start, a pause, resume method, and so on. And then in the construction of our download controller, instead of allocating a different subclass of our download controller, we would allocate a different downloader data source.

If we're wanting to perform downloads with our NSURLSession API, we check to see if NSURLSession exists. If it does, we can create a data source specific to NSURLSession. If it does not exist, we would create a data source that performs downloads with NSURL connection. And then we would finish the allocation of our download controller by saying here's the data source to use.

Then when we ask our download controller to pause or start or resume a download, instead of implementing that logic itself, it simply turns around and asks its data source, how do I actually do that? And it's the data source that implements the peculiarities of each of those APIs. And again, like with a class cluster, it is totally trivial to remove unnecessary code that we don't need anymore. So when we drop support for iOS 6 and we don't need NSURL connection anymore, we simply delete a few lines of code. That's all there is to it.

So class clusters and data sources are great ways to encapsulate feature checks for new classes, new capabilities, new architectures and so on. But they don't work very well for new methods. It's really hard to build a class cluster around a new method. It's gross. Don't do that. Instead, let's use a category.

Now, if you're not familiar with a category, in Objective C, we have the ability to add methods to other people's classes. So, for example, in your apps, if you really wished that NSString had some extra methods that you needed that were unique to the needs of your app, with a category, you can add those methods to NSString directly.

And this ability to add methods to other people's classes makes it very easy to encapsulate method checks as well. So let's go back to that example that we saw earlier with motion effects. We know that if we just invoke add motion effect on iOS 6 that our app will crash.

Not what we want, of course. And we also know that the correct way to solve this is by using that response to selector method that we learned about. But we want to keep this code easy to understand. We don't want to put the check here because that adds complexity to this code. We want to abstract it out a little bit more.

So we're going to create a category that adds a new method to UI view. This method will be named very similarly to the actual method that we care about, just with a different name. We're going to use a little prefix so that we know that it's our method. And then the implementation of this performs the check for us.

does response to selector. If it actually does exist, then we can continue on. If our add motion effect method does not exist, then we'll just drop this message on the floor and do nothing. So now instead of having our response to selector checks scattered throughout our code, our code just looks like this.

We have our new custom method that we use. And now we do not have to care whether the add motion effect method actually exists or not. That logic is abstract. It's abstracted away from this calling code, and it is totally opaque to us. We don't have to care.

Now you may be thinking, well, instead of just scattering this if check everywhere, we've scattered this TT underscore add motion effect method everywhere. Isn't that kind of the same thing? Well, not really. This is much easier to get rid of when we don't need it anymore. Simply by using the refactor rename tool in Xcode, we can, within a matter of seconds, remove all traces of this method once we don't need it anymore.

We simply rename it to the actual method it's supposed to be, add motion effect. This takes literally about three seconds, and when it's done, we can safely delete the category because we won't need it anymore. So categories are a great way to encapsulate feature checks related to new methods. I want to close by talking about the concept of backporting features.

Many of the things that we've been talking about are things that can be easily approximated on an older operating system. So, for example, the concept of downloads. On iOS 7, we have this fantastic background download API. Now, we couldn't do background downloads on iOS 6, and it's impossible to do that yourself, but we can get pretty close by approximating this with NSURL connection. We can still perform downloads, just not background downloads. So, in that case, it's worth your time to invest in this. It's best making that capability work on an older operating system. But I do not recommend trying to reimplement the game controller framework for iOS 6.

That's just way too much work. So when it comes to larger features like this, It's okay to leave out support for that feature on an older operating system. Many of you do this already when it comes to the performance of your games. You fine‑tune the performance and what effects you add to your OpenGL views depending on the device that you're running.

We saw, for example, in the announcement event just a couple of months ago how Infinity Blade takes advantage of this. On an iPhone 5S, they're able to add a lot more effects to that dragon they demoed because the GPU can handle that. They, of course, would not add those effects to the same game running on an iPhone 4.

We can take the same approach with the features and capabilities of our application. If it's worth our time to invest to make that feature work on an older operating system, great. If it's not, let's move on. Dave DeLoong So that's a look at how we can adopt new features of the iOS 7 SDK while still maintaining backwards compatibility with iOS 6.

You need never ask that question again. How do I adopt new features? You can do it. It is easy, and you can do it while still keeping your code absolutely beautiful. Dave DeLoong Thank you all for coming, and I hope you have a great rest of the day.