Developer Tools • iOS, OS X • 42:56
Objective-C is the language of choice for building great iOS and OS X apps. Dive deep into the latest advancements in Objective-C, learn how you can write more concise and less error-prone code, and find out about the advancements in Automatic Reference Counting.
Speaker: Patrick Beard
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
I'm Patrick Beard, and I work on Objective-C. Let me tell you, it's a real honor to get to work on this 30-year-old language. So Objective-C is popular, and I can see that by how many of you are here today. In the last five years, the popularity, according to the Tyobi Programming Community Index, has gone up from, well, number 45-- this is popularity in terms of search engine hits on the popular search engines-- To number six last year, and this year we've broken the top five.
Mention number five. So let's talk about some of the history, just a little bit of the history of the language. Started back in the late -- early '80s, Brad Cox, StepStone. And the Object-Oriented-C, very small language. I sort of think of it as the ukulele of languages. It's a very powerful but small language.
Then a little company you may have heard of, Next, got hold of it, added some nice features to the language, protocols, which are wonderful, and retain and release, a memory management model that allows you to write more scalable programs, and some great frameworks, many of which we're still using today.
When we got a hold of it, Apple that is, we added some new features. We made -- we streamlined your code by providing properties which automate the accessor pattern. And we added fast enumeration, which makes your code more efficient, more concise. So there's a theme here. We are simplifying the language we'd like to say. We're adding features. We added blocks, which make it a lot easier to do multi-threaded programming. And finally, Arc, which automates retain and release, makes the memory management a lot simpler in your program.
So we haven't been idle. This past year, we've been adding some additional features to the language that I'm happy to present to you today. These are going to help simplify your code and help you avoid making common mistakes. So let's start off with a very fundamental feature of the language: methods. Methods are how you give behavior to your objects.
So everybody's written, hopefully, a class like this. It has a public method that's used by the customers of your code. And it has a private method. So in this case, the public method says, let's play a song. And we have some sort of song object. And the private method is used to manage the audio, manage the audio engine that's underlying the song player.
Now, if you write it like I've written it here, with the current compilers, the previous compilers, you're going to get a warning. It says, basically, hey, I have not seen this method. You're calling it before you've even declared it. What's up with that? So let's talk about a couple ways we could fix this problem. Oh, and by the way, if you try to do this under Arc, Arc's going to complain even more. It's going to make it an error. Because it needs to know about memory management, return types, all those details.
So we can fix this by putting the private method in the public interface. Probably not a good idea. Your clients, the people who use your class, are going to be confused by that. Which one do I call? So don't do that. Another possibility is to use this whizzy new feature we call Class Extensions. Class Extensions let you segregate your private interfaces and your public interfaces, and you can put them in separate header files for separate compilation.
They also give you a way to add properties that are private, properties that might be used by the nib to actually assign a button so you can control playback of your song. This is also a detail. It doesn't really need to be in your class's header file. And with the latest software, the latest compilers, and our new Xcode IDE, we can find our outlets in our class extensions.
But for this case, this is probably overkill. This is a real simple solution. We can just reorder the methods in this particular case. However, there might be cases where you can't reorder the methods. The methods might be mutually recursive. It just may not make sense to the flow of your program. So we want to make it easier. And with the latest compiler, the way I wrote it before just works.
So let's talk about how it works. The compiler, when it compiles the implementation of your class, sort of looks ahead. It looks at all the headers of all the methods first. Then it's buffering up the implementations, the bodies of the methods, and it goes back, and it now has a complete list of methods. So methods that you didn't declare in any other place are effectively declared by the implementation itself. No warning, no problem. It just works. So now I want to talk about another fundamental feature of language, enumerated constants.
Before OS 10.5, if you were to look at our header files, and believe me, I looked, you would find your enums declared like this. And you might do so in your code as well, because it's simple. The problem with it is that the type used to represent variables that hold your enumerated constants is indeterminate. It totally depends on the data that is spanned or defined by these enum constants. So you might have negative numbers, or you might have really big numbers, really big integers. So it doesn't fit in an integer. It may require a long integer.
So when we went to 64-bit in the 64-bit transition, we realized we needed to do something about this. So we use what we call explicit type enums. This is separating out the declaration of the enum type from the definition of the constants. So you're guaranteed it's more portable between 32-bit and 64-bit. You'll always get a predictable size. But there's a downside to this. There's no formal relationship between the type and the constants themselves. So, well, we can't do any type checking, for one thing. And it makes it harder to do code completion.
So we've adopted a feature from C++11, we've added it right into Objective-C, that combines the best of these two approaches. We call it enums with fixed underlying type, and as you can see, it looks a lot like inheritance. This NSNumber formatter style is effectively a subtype of NSU integer. So this gives us better code completion. This gives us type checking. We can actually know if we use a wrong constant with one of these types.
If you're using-- if you're taking a look at our latest header files, you're going to see the introduction of this using the NSEnum macro, which is basically a macro that checks whether or not fixed underlying types are available. So you'll see this in Foundation and other frameworks that adopt this. And you can use it in your own code.
So let's give an example of what using an explicit type enum gives you. With this particular type, we've got the NSAnimation curve, which hasn't been migrated yet to use NSEnum. So we type in this variable, we hit NSAnimation because we know the constants must begin with that, and we hit escape and get our code completion going, and we're presented with all these anonymous enums. So these are enums that have just been used to generate values and don't have anything tied to them.
I happen to know NSAnimationBlocking is not a member of this type, so we could easily make a mistake when we use our code completion. When we use an enum with fixed underlying type, it's a much better experience. The pop-up can show you the types. Moreover, When you use the -wconversion warnings flag on the compiler, if we write this, this happens to be not the correct type, it's going to tell us.
It's going to tell us we made this mistake. One other benefit of this is it can now again tell you that you missed something if you're using a switch statement. There are other enums you're not handling. So you can make your code more bulletproof, added default if you don't care about the other values. So that's fixed underlying types. Now I'd like to talk about properties.
So properties are great. I think they're a success by any measure of the word. People use them. They're ubiquitous. They simplify your code, so you can focus on writing the code that you need to write to make your app work. A new feature we introduced recently was the ability to segregate your instance variables as well from the header of your class. So if you like, you can put your instance variables in the add implementation block itself.
However, if you synthesize properties, you don't need to declare the instance variable at all. We've known that. We've had that for a long time. Well, I'm happy to say, as of Xcode 4.4 and later, you don't have to write anything. Synthesis is now default. So with this, you get a fully baked model object here, this person class. Now remember, if you're not using Arc, you'll have to write a dealloc method to make sure you don't have memory leaks. So one line is all you need.
So there's some implications to the way instance variables are synthesized. Let's talk about that. When you're synthesizing properties, you need to know the name of the instance variable that you're going to get. If you use Synthesize by default, you're going to get an instance variable with the underscore prefix. We've made that the new default.
You're applauding, so you probably know why. This has been a convention that's been debated for a while, whether or not Apple reserves underscore-prefixed instance variables. We don't. We highly recommend you use them. It makes your code clearer when you're using your instance variables more directly. So, you're writing a method, for example, a description method, and you're going to access that variable directly. This is what it's going to look like. Underscore is the new default.
So to summarize how this works, this table here I'm showing in the right-hand column the name of the instance variable for a property called name. It will always be _name if you provide no accessor methods, if you provide just one of the accessor methods. But if you write your property in a sort of old-school way, then it's your job to also provide the instance variable. So the rule is, if you let us synthesize any accessor, you're going to get an instance variable. The same holds for read-only properties as well. If you let us synthesize the getter, you're going to get an instance variable. Otherwise, you won't.
So what about backwards compatibility? If you just use explicit synthesis, @synthesize name, then it's as if you had written @synthesize name equals name. We've got a lot of code out there. We've got to keep working. So if you write this description method in this scenario, then you're gonna be accessing the instance variable using-- without an underscore.
So Core Data is this powerful technology that provides an alternative way of managing your data, keeping it persistent, And letting you define your objects using data models. And a typical use of it, you will declare a class that represents the same class as -- it's a subclass of NSManagedObject, and all the attributes and relationships in the data model will be modeled as properties. So this gives you typed access if you choose. You could always use value for key and set value for key if you like, too. But this is what we see in most code.
Then when you write the implementation of these classes, the done thing has been historically to use @dynamic. @dynamic tells the compiler, hey, somebody else is providing the implementation, so we'll just keep doing that. Specifically, Core Data, NSManaged object, opts out of Synthesize by default. It does this by an attribute that's in the header file. So it wouldn't make sense for the compiler to automatically generate these implementations. They would shadow or get in the way with what Core Data provides. So just continue to use @property the way you have and use @dynamic. Things will just continue to work.
Now, if you're transitioning-- when you're transitioning to @synthesize by default, consider this warning that we provide, implicit synthesize properties. This will just tell you-- you can keep an eye on what the compiler is doing every time it synthesizes a property by default. This could be good if you have a policy in your code where you want to use explicit @synthesis, somebody adds a new property declaration, and you want to make sure you add the explicit @synthesize statement. So we have this warning.
So in summary, synthesize by default will synthesize any accessor method that you don't provide, and an instance variable will be generated unless you provide all the accessors yourself. AdSynthesize will always generate an instance variable if you use it explicitly, and a variable hasn't been declared. At dynamic now has more teeth. It actually will inhibit synthesize by default on a property-by-property basis, if that's what you need. And then core managed object, and its managed objects opt out. So that's synthesize by default.
Now for the feature you've all been waiting for. So NSNumbers are ubiquitous. They're used by lots of our frameworks. They're used for configuration data. They're used for metadata, talking about the file system when you use NSURL. They're used for core image filters. They're everywhere. And they're simple, but they require a lot of typing.
So here I have some examples. We've got some literal data, a character, an integer, long integer, floats and doubles and booleans, my, my, oh, my. We're making this a lot simpler, less typing, more straightforward. Here's what it looks like. In the same way that you prefix a string constant with an @, you can prefix a character literal with an @, and you'll get an NSNumber out of it. Same is true for integers, floating points, doubles, booleans. Really simple. It's going to save you a lot of time.
But wait, there's more. So we realized we made one part of creating NSNumbers really easy, but there are still all the cases where you compute your NSNumber values with expressions. And I find when I look at these, the expressions, which are the important part, often get lost in all the text that exists that you have to write.
So now we have defined that a parenthesized expression prefix with an @ gives you an NSNumber. Or not necessarily an NSNumber, but depending on the type of the expression, you'll get different values. So here we're indexing a string, figuring out the hexadecimal digit of a number. Here we're wrapping a Boolean that we've just read from a framework. Here we're putting a writing direction enum constant into an NSNumber because we're writing, let's say, an NS-attributed string that needs to be bidirectional.
So what I observe when you use these expressions is just your eye focuses in on what you're trying to do, and you don't really get lost in the noise. So a little bit more about strings. Well, in the same way that we added ats in front of literals, we decided it would be a great idea if we could put an at in front of a character string and get an anna string out of it. So that's what we've done. One clap.
So you read a variable from the environment, and you want to parse it, because it's a lot easier to use Foundation to parse out the elements of a path, colon delimited. So the rules are, it has to be a null-terminated C string, and in no other encodings other than ASCII or UTF-8. And it can't be null. So that's numbers.
A next very ubiquitous data type would be foundation containers, NSRAs, dictionaries, as well. These are used everywhere to represent property lists, and, you know, you use them. So we wanted to simplify the number of choices that you have when creating these. So there are a lot of choices.
There are a number of methods you can use. We have some special case forms in the NSArray class for creating an empty array, for creating a single element array. And then we have our variable arguments form, which is probably the most common method used. It has one little bugaboo about it, at least my personal bugaboo, is that you have to nil-terminate the argument list.
I've always thought, "Gosh, the compiler can tell me I need to nil-terminate it. Why can't it do it for me?" And then we have this other form where you pack up the elements of an array as a C array, and you tell it very explicitly how big that array is, how many elements should be in it.
Well, there's an inconsistency between these two forms. When you use the nil-terminated form, if any of the arguments you pass are nil, you're going to get a shorter array. I know, you're saying, that's not a bug, that's a feature, but... Not when you're trying to debug your code and you wonder why things are not working.
However, with the C array form, if you put nil as any one of the arguments, you're going to get an exception. And we think that's the better default behavior. So you know if you get one of these arrays back, it's what you expect. So now we've got a new syntax not to... Prolong it any further, the suspense. @ gives you an empty array, a single element array, multiple element array, and then this last form, well, their equivalent.
So now you have time to do other things, less typing. How does it work? Well, when you write this, the compiler does this. So any time you use one of these expressions, these array literal expressions, you're going to get the form. It would throw an exception if any of the values are incorrect.
Okay, dictionaries. With dictionaries, we have more of the same. We have more choices, and we have even more ways to make a mistake. So here we have the empty form of dictionary, single element, and now we've got dictionary with objects and keys. Couple things about this. Alternating arguments, values and keys. Values first, keys second.
Who's made a mistake with this? So I know I have, and I know I end up writing the value key one per line, comma, comma, comma. Well, with this particular case, if the value is nil, you'll get a shorter dictionary than you expected. And if the key is nil, well, it's going to say, hey, you terminated the list with an odd number of arguments. That was a bad idea. So you'll get an exception sometimes and not other times.
And again, with a fully formed way, it's a little easier to manage because you can see all the values in parallel with all the keys. And this one will always throw an exception in either case. So you can guess which one we chose. So here's what it looks like.
Dictionaries, empty dictionaries. There's the no special form. Just use the syntax. Keys and values separated by colon. You can't get it wrong. You can't forget. Multiple comma delimited key colon value. Again, I think it speaks for itself. And this last form again is the form we chose. So when you write this, this is what we do for you.
So these literals come with a couple strings attached. Ba-dum-bum. If you have an immutable array-- sorry, all containers that are created from literals are immutable. So if you need a mutable form, just--you can use the literal and call the mutable copy method. Oftentimes, we're just creating mutable containers that are empty anyway, so this may not be that common.
So what about constant containers? We can have constant strings. What about constant containers? Well, I've shown you how this is implemented. So the compiler is not going to let you write this. It's going to tell you array literals are not constant values. So the workaround is simple. Just use a plus initialize method in your class for data that you need to access.
And it's going to get called before any instance of your class is ever initialized, or any method is ever called. And it's thread safe. You just have to guard that you're not allocating it more than once by the simple test. And if you're not using Arc-- and you should be-- if you're not using Arc, you'll have to retain this value, because they're always auto-released.
Now, dictionaries pose a couple of other problems. One, to have dictionary values, you'd have to have some static data structure where the values will be pre-hashed or pre-sorted. There's actually a more fundamental problem. You can't even write this. The framework keys that we provide are almost always global variables rather than string literals. So they're not compile-time constants themselves. So you're not going to be able to do that anyway most of the time. So the same is true. If you need to initialize one of these, just use a +initialize.
Okay, that's literals. Literals make it easier to create new objects, new containers, new numbers. I want to talk about subscripting now, which makes it easier to use them. So here I have a piece of code, some kind of a song list that represents, you know, maybe keeping track of top 10 lists or something of your favorite songs. And then we have a replace song method. And it's keeping this list of songs in a mutable array. So we write a replace song method like this today. Now you can write it like this.
So the same is true for dictionaries. We have two kinds. If you want to access the element of a dictionary, read or write it, you can just use subscript notation. So let's talk about what happens when you use the syntax. So we define two kinds of subscripting, index subscripting and keyed subscripting. Index is when you use an integer as the subscript, and keyed subscripting is when you use an object.
So when you write one of these expressions, the compiler calls a method, as you might expect. calls object index subscript for index subscripting. And when you update an element of an array, it calls set object at index subscripting. So we chose these new method names so that you can have a way to have new code opt in to using the syntax. So it's optional. You don't have to use it.
Dictionaries work similarly. When you read an element, it's object for keyed subscript and set object for keyed subscript. So here's how it works. The compiler looks for the existence, the declaration of these methods. And if they are present in a class, - It enables the syntax. The element type can be any object type you choose. Doesn't have to be ID.
And the index type must be some integral type. And also the key type must be an object type. It's a very loose contract with the compiler. We consider this a sort of informal protocol. Your classes can use this. So you can make your code, your groovy song list, respond to subscripting notation.
So all these features are available in the tools that you have here today, Xcode 4.4 and 4.5, synthesized by default, the method declarations in any order you like, the fixed underlying types, the literals, the subscripting, and the expressions. The great thing about all these features is they all work not only with the latest OS, but they work on older OSes, too. It's all binary compatible.
No barrier of adoption. All right, I'd like to show you a demo now of our refactoring tool. So here we have an iOS application called WikiHow, and if you were here last year, you will remember this example. So we have in our Edit menu the refactor command, convert to modern Objective-C syntax. So we could do this target by target if we want.
And as you can see, it finds all the instances of the existing code patterns that you're familiar with and substitutes in the new syntax. Really straightforward. Here's a case where it's converting a C string that used to be wrapped in an N-string and using the new syntax. Lots of those in this code. Empty array. Here's a case where we're using an object, turning an object index into a subscript.
So real simple, shortens your code. Ah, that was one I was looking for. So here's a case where it's actually doing a nested subscripting expression. It's a pretty sophisticated conversion tool. So that's what you have available to you if you choose to use it.
[Transcript missing]
And it works. Okay. Thank you. Thank you. Thank you. So now I'd like to shift gears and talk a little bit about using C++ with Arc. So if you've been using Arc for any time, you'll be aware of this issue. You can't use C structs directly to hold object pointers under Arc. If you try, you'll get this compile error.
So why do we prohibit this? One reason is that C structs can be uninitialized. You use them on the stack. So they can have garbage values in them that are not going to be tolerated well by Arc. If you were to release a garbage value, odds are you'll crash. They can also just be copied willy-nilly. You can pass them as arguments. You can use mem copy. And then Arc skips out on the ability to do any kind of ownership transfer.
Of course, nothing happens to these when they go out of scope, so memory is going to leak if you store retained pointers in them. So to fix your code so that it works better under Arc, you can use-- convert all your structs to classes if you want. You can go whole hog and use properties and synthesize by default.
Another possible solution is to use Objective-C++. If you have some structs that you cannot convert, that's actually one of the benefits of Objective-C++ is it gives us a real good way of using a lot of existing code. It's a good porting tool. So how does it work? Well, under Arc, if you have object pointers in your C structs, or your C++ structs, they're no longer considered pods or plain old data types. The compiler implicitly generates a bunch of code for you. So let's look at that. The constructor will be generated at a minimum to automatically initialize the variables, the object pointer variables, and the destructor will automatically release.
It'll also generate copy constructors and a default operator assign operator equals that do the right retain-release dance for you. And now we have C++11, which gives you move semantics. And so we'll automatically generate proper move constructors and move operators. So this makes your code more efficient. Especially if instances of these objects are inside, you know, STL vectors or something. So not to worry, this looks like a lot of detail, but the compiler does it for you. You don't have to worry about it.
This also works very generally with STL classes, as you might expect. You could have a class where you want to visit all the subviews of a window, for example. And to do a proper depth-first search algorithm, you could use a vector to keep track of where you are. So when you add an element of an object pointer type to a template class, you're going to get the right behavior. It's going to take a strong reference in this case.
So I had another example where you have weak pointers. Weak pointers are supported as well in STL vectors. The compiler will support them, and if objects are removed behind your back out of a weak vector, a weak container, say you have a weak vector for managing observers and you don't want to have any retain cycles.
You would probably have to write to clean up, to make your vector smaller, periodically some kind of a compacting operation. So here we're using a new feature in C++11 called lambdas. So C++11 lambdas just give you a simple way. They're very analogous to blocks. And in fact, if you want, you can just use blocks directly in place of C++ lambdas. The compiler will automatically construct a lambda that just wraps the block for you. So you can just stick with the notation you're comfortable with.
So Arc is a great technology. It automates memory management with CocoaObjects. But it doesn't give us any support, really, for core foundation. Core foundation is something that sometimes we all have to dabble with. So the stickiest issue is if you are using some kind of core foundation API, like the address book, and you're wanting to convert values that you get from those APIs back into Objective-C objects. So like, for example, here we're doing some kind of a query on the address book, and we want to make an NSArray out of it.
Well, like we introduced last year, we provide this CAS notation called Bridge Transfer. And this does what you would expect. It's transferring ownership from using explicit CFRetain, CFRelease into NSArray semantics using Arc. And as we recommend, rather than using that bridge syntax, that you just go ahead and use CF Bridging Release, which does the same thing. So this is when you have the need to transfer ownership.
However, we've improved some things. Last year, we also talked about if you're going to just pass So, for Cocoa objects, two core Foundation APIs, you had to write this other cast form, the BridgeCast, which just basically said, "There's nothing fancy going on here. We're just using one of our objects as one of these other types." So, this is what you needed to do the bridging, toll-free bridging, the minimum.
Well, now we've added a new capability. We've added annotations to the header files for core Foundation. We'll be adding more to all the sort of core Foundation-style APIs. What this annotation means, these have been validated to be consistent. They've been annotated to have whatever lifetime semantics are necessary. Therefore, you can just use them, no bridge cast required. So this is what it looks like.
So that should make your code a little bit easier. So now let's talk about garbage collection. So Garbage Collection is a feature that has been superseded by Arc, which provides a very similar experience for managing memory automatically. And the benefit of Arc is that it's available on both platforms, OS X, iOS.
Our frameworks, historically, have been written in a way that is optimized for retain-release. So things happen in a predictable, deterministic order, and Arc really fits those frameworks well. On our mobile devices, performance is critical. Responsiveness is critical. So when you're scrolling your iPad, You want to use as few resources as possible and let the app shine. So we know that Arc enables this.
Also, battery life is really critical. So Arc gives you a way of measuring the performance of your app and making sure that it remains predictable and efficient. So garbage collection is deprecated in Mountain Lion. And you can use our Migrator tool, the refactoring tool that we showed you earlier, to migrate your code from Garbage Collection to Arc. And a lot of times, it goes real simply.
[Transcript missing]
So for more information about these issues, and make contact with us, please contact Michael Jurowicz. And if you want to read more about the language extensions that I talked about today, then there's a website, the LLVM website has information about the language extensions. And there's always the Apple Developer Forums to ask questions.
Also, if you want more information, we have two labs, one starting at 2 today and another one starting at... Sorry, we have related sessions. The very next session coming up is adopting reference counting, more information on that. And then tomorrow we have a talk about migrating to Objective-C. So thanks for coming. This is what we talked about today, the new feature synthesized by default, the better -- lack of forward declarations, fixed underlying type enums and literals, subscripting, and GC is deprecated. So thanks a lot. Thanks for your time.