As a courtesy, this is a full free rendering of my book, Programming iOS 6, by Matt Neuburg. Copyright 2013 Matt Neuburg. Please note that this edition is outdated; the current books are iOS 13 Programming Fundamentals with Swift and Programming iOS 13. If my work has been of help to you, please consider purchasing one or both of them, or you can reward me through PayPal at http://www.paypal.me/mattneub. Thank you!
One of the first object-based programming languages to achieve maturity and widespread dissemination was Smalltalk. It was developed during the 1970s at Xerox PARC under the leadership of Alan Kay and started becoming widely known in 1980. The purpose of Objective-C, created by Brad Cox and Tom Love in 1986, was to build Smalltalk-like syntax and behavior on top of C. Objective-C was licensed by NeXT in 1988 and was the basis for its application framework API, NeXTStep. Eventually, NeXT and Apple merged, and the NeXT application framework evolved into Cocoa, the framework for Mac OS X applications, still revolving around Objective-C. That history explains why Objective-C is the base language for iOS programming. (It also explains why Cocoa class names often begin with “NS” — it stands for “NeXTStep.”)
Having learned the basics of C (Chapter 1) and the nature of object-based programming (Chapter 2), you are ready to meet Objective-C. This chapter describes Objective-C structural fundamentals; the next two chapters provide more detail about how Objective-C classes and instances work. (A few additional features of the language are discussed in Chapter 10.) As with the C language, my intention is not to describe the Objective-C language completely, but to provide a practical linguistic grounding, founded on my own experience of those aspects of the language that need to be firmly understood as a basis for iOS programming.
In C, every variable must be declared to be of some type. In an object-based language such as Objective-C, an instance’s type is its class. The C language includes very few basic data types. To facilitate the multiplicity of class types required by its object-based nature, Objective-C takes advantage of C pointers. So, in Objective-C, if a variable is an instance of the class MyClass, that variable is of type MyClass*
— a pointer to a MyClass. In general, in Objective-C, a reference to an instance is a pointer and the name of the data type of what’s at the far end of that pointer is the name of the instance’s class.
Note the convention for capitalization. Variable names tend to start with a lowercase letter; class names tend to start with an uppercase letter.
As I mentioned in Chapter 1, the fact that a reference to an instance is a pointer in Objective-C will generally not cause you any difficulties, because pointers are used consistently throughout the language. For example, a message to an instance is directed at the pointer, so there is no need to dereference the pointer. Indeed, having established that a variable representing an instance is a pointer, you’re likely to forget that this variable even is a pointer and just work directly with that variable:
NSString* s = @"Hello, world!"; NSString* s2 = [s uppercaseString];
Having established that s
is an NSString*
, you would never dereference s
(that is, you would never speak of *s
) to access the “real” NSString. So it feels as if the pointer is the real NSString. Thus, in the previous example, once the variable s
is declared as a pointer to an NSString, the uppercaseString
message is sent directly to the variable s
. (The uppercaseString
message asks an NSString to generate and return an uppercase version of itself; so, after that code, s2
is @"HELLO, WORLD!"
)
The tie between a pointer, an instance, and the class of that instance is so close that it is natural to speak of an expression like MyClass*
as meaning “a MyClass instance,” and of a MyClass*
value as “a MyClass.” A Objective-C programmer will say simply that, in the previous example, s
is an NSString, that uppercaseString
returns “an NSString,” and so forth. It is fine to speak like that, and I do it myself (and will do it in this book) — provided you remember that this is a shorthand. Such an expression means “an NSString instance,” and because an instance is represented as a C pointer, it means an NSString*
, a pointer to an NSString.
Although the fact that instance references in Objective-C are pointers does not cause any special difficulty, you must still be conscious of what pointers are and how they work. As I emphasized in Chapter 1, when you’re working with pointers, you must keep in mind the special meaning of your actions. So here are some basic facts about pointers that you should keep in mind when working with instance references in Objective-C.
Forgetting the asterisk in an instance declaration is a common beginner mistake, and will net you a mysterious compiler error message, such as “Interface type cannot be statically allocated.”
Merely declaring an instance reference’s type doesn’t bring any instance into existence. For example:
NSString* s; // only a declaration; no instance is pointed to
After that declaration, s
is typed as a pointer to an NSString, but it is not in fact pointing to an NSString. You have created a pointer, but you haven’t supplied an NSString for it to point to. It’s just sitting there, waiting for you to point it at an NSString, typically by assignment (as we did with @"Hello, world!"
earlier). Such assignment initializes the variable, giving it an actual meaningful value of the proper type.
You can declare a variable as an instance reference in one line of code and initialize it later, like this:
NSString* s; // ... time passes ... s = @"Hello, world!";
But this is not common. It is much more common, wherever possible, to declare and initialize a variable all in one line of code:
NSString* s = @"Hello, world!";
Declaration without initialization, before the advent of ARC in iOS 5 (Chapter 12), created a dangerous situation:
NSString* s;
What is s
after a mere declaration like that? It could be anything. But it is claiming to be a pointer to an NSString, and so your code might proceed to treat it as a pointer to an NSString. But it is pointing at garbage. A pointer pointing at garbage is liable to cause serious trouble down the road when you accidentally try to use it as an instance.
Sending a message to a garbage pointer, or otherwise treating it as a meaningful instance, can crash your program. Even worse, it might not crash your program: it might cause your program to behave very, very oddly instead — and figuring out why can be difficult.
For this reason, if you aren’t going to initialize an instance reference pointer at the moment you declare it by assigning it a real value, it’s a good idea to assign it nil:
NSString* s = nil;
A small but delightful bonus feature of using ARC is that this assignment is performed for you, implicitly and invisibly, as soon as you declare a variable without initializing it:
NSString* s; // under ARC, s is immediately set to nil for you
This prevents the existence of a garbage pointer, and could save you from yourself by preventing a crash when you accidentally use s
as an instance without initializing it. Nevertheless, long years of habit have trained me to initialize or explicitly set to nil an instance pointer as soon as I declare it, and you’ll see that I continue to do so in examples in this book.
What is nil? It’s simply a form of zero — the form of zero appropriate to an instance reference. The nil value simply means: “This instance reference isn’t pointing to any instance.” Indeed, you can test an instance reference against nil as a way of finding out whether it is in fact pointing to a real instance. This is an extremely common thing to do:
if (nil == s) // ...
As I mentioned in Chapter 1, the explicit comparison with nil isn’t strictly necessary; because nil is a form of zero, and because zero means false in a condition, you can perform the same test like this:
if (!s) // ...
I do in fact write nil tests in that second form all the time, but some programmers would take me to task for bad style. The first form has the advantage that its real meaning is made explicit, rather than relying on a cute implicit feature of C. The first form places nil first in the comparison so that if the programmer accidentally omits an equal sign, performing an assignment instead of a comparison, the compiler will catch the error (because assignment to nil is illegal).
Many Cocoa methods use a return value of nil, instead of an expected instance, to signify that something went wrong. You are supposed to capture this return value and test it for nil in order to discover whether something did go wrong. For example, the documentation for the NSString class method stringWithContentsOfFile:encoding:error:
says that it returns “a string created by reading data from the file named by path
using the encoding, enc
. If the file can’t be opened or there is an encoding error, returns nil.” So, as I described in Chapter 1, your next move after calling this method and capturing the result should be to test that result against nil, just to make sure you’ve really got an instance now:
NSString* path = // ... whatever; NSStringEncoding enc = // ... whatever; NSError* err = nil; NSString* s = [NSString stringWithContentsOfFile:path encoding:enc error:&err]; if (nil == s) // oops! something went wrong...
You should now be wondering about the implications of a nil-value pointer for sending a message to a noninstance. For example, you can send a message to an NSString instance like this:
NSString* s2 = [s uppercaseString];
That code sends the uppercaseString
message to s
. So s
is supposedly an NSString instance. But what if s
is nil? With some object-based programming languages, sending a message to nil constitutes a runtime error and will cause your program to terminate prematurely (REALbasic and Ruby are examples). But Objective-C doesn’t work like that. In Objective-C, sending a message to nil is legal and does not interrupt execution. Moreover, if you capture the result of the method call, it will be a form of zero — which means that if you assign that result to an instance reference pointer, it too will be nil:
NSString* s = nil; // now s is nil NSString* s2 = [s uppercaseString]; // now s2 is nil
Whether this behavior of Objective-C is a good thing is a quasi-religious issue and a subject of vociferous debate among programmers. It is useful, but it is also extremely easy to be tricked by it. The usual scenario is that you accidentally send a message to a nil reference without realizing it, and then later your program doesn’t behave as expected. Because the point where the unexpected behavior occurs is later than the moment when the nil pointer arose in the first place, the genesis of the nil pointer can be difficult to track down (indeed, it often fails to occur to the programmer that a nil pointer is the cause of the trouble in the first place).
Short of peppering your code with tests to ascertain that your instance reference pointers are not accidentally nil, which is not generally a good idea, there isn’t much you can do about this. This behavior is strongly built into the language and is not going to change. It’s just something you need to be aware of.
If, on the other hand, a method call can return nil, be conscious of that fact. Don’t assume that everything will go well and that it won’t return nil. On the contrary, if something can go wrong, it probably will. For example, to omit the nil test after calling stringWithContentsOfFile:encoding:error:
is just stupid. I don’t care if you know perfectly well that the file exists and the encoding is what you say it is — test the result for nil!
As I said in Chapter 1, assigning to a pointer does not mutate the value at the far end of the pointer; rather, it repoints the pointer. Moreover, assigning one pointer to another repoints the pointer in such a way that both pointers are now pointing to the very same thing. Failure to keep these simple facts firmly in mind can have results that range from surprising to disastrous.
For example, instances in general are usually mutable: they typically have instance variables that can change. If two references are pointing at one and the same instance, then when the instance is mutated by way of one reference, that mutation also affects the instance as seen through the other reference. To illustrate, pretend that we’ve implemented the Stack class described in the previous chapter:
Stack* myStack1 = // ... create Stack instance and initialize myStack1 ... ; Stack* myStack2 = myStack1; [myStack1 push: @"Hello"]; [myStack1 push: @"World"]; NSString* s = [myStack2 pop];
After we pop myStack2
, s
is @"World"
even though nothing was ever pushed onto myStack2
(and the stack myStack1
contains only @"Hello"
even though nothing was ever popped off of myStack1
). That’s because we did push two strings onto myStack1
and then pop one string off myStack2
, and myStack1
is myStack2
— in the sense that they are both pointers to the very same stack instance. That’s perfectly fine, as long as you understand and intend this behavior.
In real life, you’re likely to pass an instance off to some other object, or to receive it from some other object:
Stack* myStack = // ... create Stack instance and initialize myStack ... ; // ... more code might go here ... [myObject doSomethingWithThis: myStack]; // pass myStack to myObject
After that code, myObject
has a pointer to the very same instance we’re already pointing to as myStack
. So we must be careful and thoughtful. The object myObject
might mutate myStack
right under our very noses. Even more, the object myObject
might keep its reference to the stack instance and mutate it later — possibly much later, in a way that could surprise us. This is possible because instances can have instance variables that point to other objects, and those pointers can persist as long as the instances themselves do. This kind of shared referent situation can be intentional, but it is also something to watch out for and be conscious of (Figure 3.1).
Another possible misunderstanding is to imagine that the assignment myStack2 = myStack1
somehow makes a new, separate instance that duplicates myStack1
. That’s not at all the case. It doesn’t make a new instance; it just points myStack2
at the very same instance that myStack1
is pointing at. It may be possible to make a new instance that duplicates a given instance, but the ability to do so is not a given and it is not going to happen through mere assignment. (For how a separate duplicate instance might be generated, see the NSCopying protocol and the copy
method mentioned in Chapter 10.)
The pointer nature of instance references in Objective-C also has implications for management of memory. The scope, and in particular the lifetime, of variables in pure C is typically quite straightforward: if you bring a piece of variable storage into existence by declaring that variable within a certain scope, then when that scope ceases to exist, the variable storage ceases to exist. That sort of variable is called automatic (K&R 1.10). So, for example:
void myFunction() { int i; // storage for an int is set aside i = 7; // 7 is placed in that storage } // the scope ends, so the int storage and its contents vanish
But in the case of a pointer, there are two pieces of memory to worry about: the pointer itself, which is an integer signifying an address in memory, and whatever is at that address, at the far end of that pointer. Nothing about the C language causes the destruction of what a pointer points to when the pointer itself is automatically destroyed as it goes out of scope:
void myFunction() { NSString* s = @"Hello, world!"; // pointer and NSString NSString* s2 = [s uppercaseString]; // pointer and NSString } // the two pointers go out of existence... // ... but what about the two NSStrings they point to?
Some object-based programming languages in which a reference to an instance is a pointer do manage automatically the memory pointed to by instance references (REALbasic and Ruby are examples). But Objective-C, at least the way it’s implemented when you’re programming for iOS, is not one of those languages. Because the C language has nothing to say about the automatic destruction of what is pointed to by a reference to an instance, Objective-C implements an explicit mechanism for the management of memory. I’ll talk in a later chapter (Chapter 12) about what that mechanism is and what responsibilities for the programmer it entails. Fortunately, under ARC, those responsibilities are fewer than they used to be; but memory must still be managed, and you must still understand how memory management works.
An Objective-C method is defined as part of a class. It has three aspects:
void
.
As you’ve doubtless gathered, the syntax for sending a message to an object involves square brackets. The first thing in the square brackets is the object to which the message is to be sent; this object is the message’s receiver. Then follows the message:
NSString* s2 = [s uppercaseString]; // send "uppercaseString" message to s ... // ... (and assign result to s2)
If the message is a method that takes parameters, each corresponding argument value comes after a colon:
[myStack1 push: @"Hello"]; // send "push:" message to myStack1 ... // ...with one argument, the NSString @"Hello"
To send a message to a class (calling a class method), you can represent the class by the literal name of the class:
NSString* s = [NSString string]; // send "string" message to NSString class
To send a message to an instance (calling an instance method), you’ll need a reference to an instance, which (as you know) is a pointer:
NSString* s = @"Hello, world!"; // s is initialized as an NSString instance NSString* s2 = [s uppercaseString]; // send "uppercaseString" message to s
You can send a class method to a class, and an instance method to an instance, no matter how you got hold of and represent the class or the instance. For example, @"Hello, world!"
is itself an NSString instance, so it’s legal to say:
NSString* s2 = [@"Hello, world!" uppercaseString];
If a method takes no parameters, then its name contains no colons, like the NSString instance method uppercaseString
. If a method takes one parameter, then its name contains one colon, which is the final character of the method name, like the hypothetical Stack instance method push:
. If a method takes two or more parameters, its name contains that number of colons. In the minimal case, its name ends with that number of colons. For example, a method taking three parameters might be called hereAreThreeStrings:::
. To call it, we split the name after each colon and follow each colon with an argument, which looks like this:
[someObject hereAreThreeStrings: @"string1" : @"string2" : @"string3"];
That’s a legal way to name a method, but it isn’t very common, mostly because it isn’t very informative. Usually the name will have more text; in particular, the part before each colon will describe the parameter that follows that colon.
For example, there’s a UIColor class method for generating an instance of a UIColor from four CGFloat numbers representing its red, green, blue, and alpha (transparency) components, and it’s called colorWithRed:green:blue:alpha:
. Notice the clever construction of this name. The colorWith
part tells something about the method’s purpose: it generates a color, starting with some set of information. All the rest of the name, Red:green:blue:alpha:
, describes the meaning of each parameter. And you call it like this:
UIColor* c = [UIColor colorWithRed: 0.0 green: 0.5 blue: 0.25 alpha: 1.0];
The space after each colon in the method call is optional. (Space before a colon is also legal, though in practice one rarely sees this.)
The rules for naming an Objective-C method, along with the conventions governing such names (like trying to make the name informative about the method’s purpose and the meanings of its parameters), lead to some rather long and unwieldy method names, such as getBytes:maxLength:usedLength:encoding:options:range:remainingRange:
. Such verbosity of nomenclature is characteristic of Objective-C. Method calls, and even method declarations, are often split across multiple lines to prevent a single line of code from becoming so long that it wraps within the editor, as well as for clarity.
The declaration for a method has three parts:
+
or -
, meaning that the method is a class method or an instance method, respectively.
So, for example, Apple’s documentation tells us that the declaration for the UIColor class method colorWithRed:green:blue:alpha:
is:
+ (UIColor*) colorWithRed: (CGFloat) red green: (CGFloat) green blue: (CGFloat) blue alpha: (CGFloat) alpha
(Note that I’ve split the declaration into two lines, for legibility and to fit onto this page. The documentation puts it all on a single line.)
Make very sure you can read this declaration! You should be able to look at it and say to yourself instantly, “The name of this method is colorWithRed:green:blue:alpha:
. It’s a class method that takes four CGFloat parameters and returns a UIColor.”
It is not uncommon, outside of code, to write a method’s name along with the plus sign or the minus sign, to make it clear whether this is a class method or an instance method. So you might speak informally of “-uppercaseString,” just as a way of reminding yourself or a reader that this is an instance method. Again outside of code, it is not uncommon, especially when communicating with other Objective-C programmers, to speak of a method’s name along with the class in which this method is defined. So you might say “NSString’s -uppercaseString,”
or even something like “-[NSString uppercaseString].” Notice that that isn’t code, or even pseudo-code, because you are not actually speaking of a method call, and in any case you could never send the uppercaseString
message to the NSString class; it’s just a compact way of saying, “I’m talking about the uppercaseString
that’s an instance method of NSString.”
Wherever in a method call an object of a certain type is supposed to appear, you can put another method call that returns that type. Thus you can nest method calls. A method call can appear as the message’s receiver:
NSString* s = [[NSString string] uppercaseString]; // silly but legal
That’s legal because NSString’s class method string
returns an NSString instance (formally, an NSString*
value, remember), so we can send an NSString instance method to that result. Similarly, a method call can appear as an argument in a method call:
[myStack push: [NSString string]]; // ok if push: expects NSString* parameter
However, I must caution you against overdoing that sort of thing. Code with a lot of nested square brackets is very difficult to read (and to write). Furthermore, if one of the nested method calls happens to return nil unexpectedly, you have no way to detect this fact. It is often better, then, to be even more verbose and declare a temporary variable for each piece of the method call. Just to take an example from my own code, instead of writing this:
NSArray* arr = [[MPMediaQuery albumsQuery] collections];
I might write this:
MPMediaQuery* query = [MPMediaQuery albumsQuery]; NSArray* arr = [query collections];
Even though the first version is quite short and legible, and even though in the second version the variable query
will never be used again — it exists solely in order to be the receiver of the collections
message in the second line — it is worth creating it as a separate variable. For one thing, it makes this code far easier to step through in the debugger later on, when I want to pause after the albumsQuery
call and see whether the expected sort of result is being returned.
The data type returned by a method, together with the data types of each of its parameters in order, constitute that method’s signature. It is illegal for two methods of the same type (class method or instance method) to exist in the same class with the same name but different signatures.
So, for example, you could not have two MyClass instance methods called myMethod
, one of which returns void and one of which returns an NSString. Similarly, you could not have two MyClass instance methods called myMethod:
, both returning void, one taking a CGFloat parameter and one taking an NSString parameter.
An attempt to violate this rule will be stopped dead in its tracks by the compiler, which will announce a “duplicate declaration” error.
The reason for this rule is that if two such conflicting methods were allowed to exist, there would be no way to determine from a method call to one of them which method was being called.
You might think that the issue could be decided by looking at the types involved in the call. If one myMethod:
takes a CGFloat parameter and the other myMethod:
takes an NSString parameter, you might think that when myMethod:
is called, Objective-C could look at the actual argument and realize that the former method is meant if the argument is a CGFloat and the latter if the argument is an NSString. But Objective-C doesn’t work that way. There are languages that permit this feature, called overloading, but Objective-C is not one of them.
It isn’t uncommon for an Objective-C method to require an unknown number of parameters.
A good example is the NSArray class method arrayWithObjects:
, which looks from the name as if it takes one parameter but in fact takes any number of parameters, separated by comma. The parameters are the objects of which the NSArray is to consist. The trick here, however, which you must discover by reading the documentation, is that the list must end with nil. The nil is not one of the objects to go into the NSArray (nil isn’t an object, so an NSArray can’t contain nil); it’s to show where the list ends.
So, here’s a correct way to call the arrayWithObjects:
method:
NSArray* pep = [NSArray arrayWithObjects:@"Manny", @"Moe", @"Jack", nil];
The declaration for arrayWithObjects:
uses three dots to show that a comma-separated list is legal:
+ (id)arrayWithObjects:(id)firstObj, ... ;
Without the nil terminator, the program will not know where the list ends, and bad things will happen when the program runs, as it goes hunting off into the weeds of memory, incorporating all sorts of garbage into the NSArray that you never meant to have incorporated.
Forgetting the nil terminator is a common beginner error, but not as common as it used to be: by a bit of deep-C voodoo, the Objective-C compiler now notices if you’ve forgotten the nil, and warns you (“missing sentinel in method dispatch”). Even though it’s just a warning, don’t run that code!
Another way to avoid forgetting the nil terminator is to avoid calling arrayWithObjects:
altogether; this is now possible starting with LLVM compiler version 4.0 (Xcode 4.4 or later), which allows you to form a literal NSArray object directly, using @[...]
syntax, like this:
NSArray* pep = @[@"Manny", @"Moe", @"Jack"];
That’s just a notation, a kind of syntactic sugar; behind the scenes, arrayWithObjects:
is presumably still being called for you. But it’s being called for you correctly, nil terminator and all, so this notation is much more bullet-proof than explicitly calling arrayWithObjects:
yourself; plus it’s a lot less typing. I’ll be using this new Objective-C notation for literal arrays throughout this book.
Nevertheless, you will still encounter other Objective-C methods that do have a parameter that’s a nil-terminated list of variable length. For example, there’s the UIAppearance protocol class method appearanceWhenContainedIn:
(Chapter 25), or UIAlertView’s initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:
(Chapter 26). It’s a pity that Apple hasn’t somehow tweaked Objective-C or these methods to avoid the use of the nil terminator; for instance, they could have made the variable-length list parameter into an NSArray parameter instead. But until they do, knowing how to call such methods remains important.
The C language has explicit provision for argument lists of unspecified length, which Objective-C methods such as arrayWithObjects:
are using behind the scenes. I’m not going to explain the C mechanism, because I don’t expect you’ll ever write a method or function that requires it; see K&R 7.3 if you need the gory details.
Objective-C messaging is dynamic, meaning that the compiler takes no formal responsibility for whether a particular object is a legal recipient of a given message. That’s because whether an object can deal with a message sent to it isn’t decided until the program actually runs and the message actually arrives. Objective-C has various devices for dealing at runtime with a message that doesn’t correspond directly to a method, and for all the compiler knows, one of them might come into play in this case. For example, at the time the program runs, the recipient of the message might be nil — and it’s harmless to send any message to nil.
Thus, it is theoretically legal to direct a message at an object with no corresponding method. The only guardian against this possibility is the compiler. Before ARC, the compiler was not a very strong guardian in this respect. For example:
NSString* s = @"Hello, world!"; [s rockTheCasbah]; // without ARC, compiler warns
An NSString has no method rockTheCasbah
. But the (non-ARC) compiler will not stop you from running a program containing this code; it’s legal. The compiler will warn you, but it won’t stop you.
There are actually two possible warnings:
rockTheCasbah
method is defined anywhere in your code, the compiler will say: “Instance method ‘-rockTheCasbah’ not found (return type defaults to ‘id’).” Without going into the details, what the compiler means is: “I know of no instance method rockTheCasbah
, so I can’t check its signature against the return type and arguments you’re actually using, so I’ll just make some loose assumptions and let it pass.”
rockTheCasbah
method is defined somewhere in your code, the compiler will say: “‘NSString’ may not respond to ‘rockTheCasbah’.” This means: “There’s a rockTheCasbah
method, all right, but you seem to be sending the rockTheCasbah
method to an instance of a class that doesn’t have it as an instance method.”
This is a good example of what I meant in Chapter 2 when I said that sending a message and calling a method were not the same thing. The compiler is saying that NSString has no rockTheCasbah
instance method, but that it isn’t going to stop you from sending an NSString a rockTheCasbah
message. At runtime, the object that receives the rockTheCasbah
message might be able to deal with it, for all the compiler knows.
With ARC, however, the compiler is much stricter.
The example above won’t compile at all under ARC!
The compiler declares a fatal compilation error: “Receiver type ‘NSString’ for instance message does not declare a method with selector ‘rockTheCasbah’.” There is no NSString method rockTheCasbah
, and by golly the compiler isn’t going to let you send the rockTheCasbah
message to an NSString, and that’s final.
This is another of those delightful secondary benefits of using ARC. In order to do what it primarily does (manage memory), ARC must insist on more information about classes and their methods than the Objective-C standard calls for. Here, ARC is demanding that you prove that an NSString can respond to rockTheCasbah
, or it won’t let you run this code at all. (Nevertheless, if you really want to, you can slip past even ARC’s stringent guardianship; I’ll explain how in the next section.)
Let us assume for a moment that we are compiling without ARC, or that we have somehow tricked even ARC into letting us compile successfully. Warning or no warning, we are now ready to run a program that sends the rockTheCasbah
message to an NSString, and damn the consequences. What might those consequences be? Quite simply, if you send a message to an object that can’t deal with it, your program will crash at that instant. So, for example, our attempt to send an NSString the rockTheCasbah
message will crash our program, with a message (in the console log) of this form: “-[NSCFConstantString rockTheCasbah]: unrecognized selector sent to instance 0x3048.”
The important thing here is the phrase unrecognized selector.
The term “selector”
is roughly equivalent to “message,”
so this is a way of saying that a certain instance was sent a message it couldn’t deal with. The console message also tries to tell us what instance this was. 0x3048
is the value of the instance pointer; it is the address in memory to which our NSString*
variable s
was actually pointing. (Never mind why the NSString is described as an NSCFConstantString; this has to do with NSString’s implementation behind the scenes.)
(Strictly speaking, I should not say that a situation like this will “crash our program.” What it will actually do is to generate an exception, an internal message as the program runs signifying that something bad has happened. It is possible for Objective-C code to “catch” an exception, in which case the program will not crash. The reason the program crashes, technically, is not that a message was sent to an object that couldn’t handle it, but that the exception generated in response wasn’t caught. That’s why the crash log may also say, “Terminating app due to uncaught exception.”)
One way to silence the compiler when it warns in the way I’ve just described is by typecasting. A typecast, however, is not a viable way of fixing the problem unless it also tells the truth. It is perfectly possible to lie to the compiler by typecasting; this is not nice, and is not likely to yield nice consequences.
For example, suppose we’ve defined a class MyClass that does contain an instance method rockTheCasbah
. As a result, it is fine with the compiler if you send the rockTheCasbah
message to a MyClass, although it is not fine to send the rockTheCasbah
message to an NSString. So you can silence the compiler by claiming that an NSString instance is a MyClass instance:
NSString* s = @"Hello, world!"; [(MyClass*)s rockTheCasbah];
The typecast silences the compiler; there is no warning. Notice that the typecast is not a value conversion; it’s merely a claim about what the type will turn out to be at runtime. You’re saying that when the program runs, s
will magically turn out to be a MyClass instance. Because MyClass has a rockTheCasbah
instance method, that silences the compiler. Of course, you’ve lied to the compiler, so when the program runs it will crash anyway, in exactly the same way as before! You’re still sending an NSString a message it can’t deal with, so the very same exception about sending an unrecognized selector to an NSCFConstantString instance will result. So don’t do that!
Sometimes, however, typecasting to silence the compiler is exactly what you do want to do. This situation quite often arises in connection with class inheritance.
We haven’t discussed class inheritance yet, but I’ll give an example anyway. Let’s take the built-in Cocoa class UINavigationController. Its topViewController
method is declared to return a UIViewController instance. In real life, though, it is likely to return an instance of some class you’ve created. So in order to call a method of the class you’ve created on the instance returned by topViewController
without upsetting the compiler, you have to reassure the compiler that this instance really will be an instance of the class you’ve created. That’s what I’m doing in this line from one of my own apps:
[(RootViewController*)[navigationController topViewController] setAlbums: arr];
The expression (RootViewController*)
is a typecast in which I’m assuring the compiler that at this moment in the program, the value returned by the topViewController
method call will in fact be an instance of RootViewController, which is my own defined class. The typecast silences the compiler when I send this instance the setAlbums:
message, because my RootViewController class has a setAlbums:
instance method and the compiler knows this. And the program doesn’t crash, because I’m not lying: this topViewController
method call really will return a RootViewController instance.
Objective-C also provides a special type designed to silence the compiler’s worries about object data types altogether. This is the id
type. An id
is a pointer, so you don’t say id*
. It is defined to mean “an object pointer,” plain and simple, with no further specification. Thus, every instance reference is also an id
.
Use of the id
type causes the compiler to stop worrying about the relationship between object types and messages. The compiler can’t know anything about what the object will really be, so it throws up its hands and doesn’t warn about anything. Moreover, any object value can be assigned or typecast to an id
, and a value typed as an id
can be assigned where any object type is expected. The notion of assignment includes parameter passing. So you can pass a value typed as an id
as an argument where a parameter of some particular object type is expected, and you can pass any object as an argument where a parameter of type id
is expected. (I like to think of an id
as analogous to both type AB blood and type O blood: it is both a universal recipient and a universal donor.) So, for example:
NSString* s = @"Hello, world!"; id unknown = s; [unknown rockTheCasbah];
The second line is legal, because any object value can be assigned to an id
. The third line doesn’t generate any compiler warning, because any message can be sent to an id
. (Of course the program will still crash when it actually runs and unknown
turns out to be an NSString — which is incapable of receiving of the rockTheCasbah
message!)
That trick works even under ARC, with one caveat. ARC is willing to let that code compile — but only if a matching rockTheCasbah
method is defined somewhere in your code (even if it isn’t an NSString method).
If there’s no such method, ARC will stop you with a different error: “No known instance method for selector ‘rockTheCasbah’.” This is another way of saying the same thing the non-ARC compiler said earlier: “I know of no instance method rockTheCasbah
, so I can’t check its signature against the return type and arguments you’re actually using.” But instead of implicitly adding, “So I’ll just make some loose assumptions and let it pass,” ARC is stricter. After all, even without knowing what class unknown
will turn out to be when the program runs, ARC can be pretty sure that that class won’t have a rockTheCasbah
method, because no known class has a rockTheCasbah
method. So ARC, like a good guardian, continues to bar the way.
If, however, a matching rockTheCasbah
method is defined somewhere in your code, even though it isn’t an NSString method, ARC now takes its hands off the tiller entirely, and permits the program to compile and run without warning. You are now sending a message to an id
, and an id
can legally receive any message. If you crash at runtime, that’s your problem; ARC can’t save you from yourself.
If an id
’s ability to receive any message reminds you of nil, it should. I have already said that nil is a form of zero; I can now specify what form of zero it is. It’s zero cast as an id
. Of course, it still makes a difference at runtime whether an id
is nil or something else; sending a message to nil won’t crash the program, but sending an unknown message to an actual object will.
Thus, id
is a device for turning off the compiler’s type checking altogether. Concerns about what type an object is are postponed until the program is actually running. All the compiler can do is intelligently analyze your code to see if you might be making a mistake that could matter at runtime. Using id
turns off this part of the compiler’s intelligence and leaves you to your own devices.
I do not recommend that you make extensive use of id
to live in a world of pure dynamism. The compiler is your friend; you should let it use what intelligence it has to catch mistakes in your code. Thus, I almost never declare a variable or parameter as an id
. I want my object types to be specific, so that the compiler can help check my code.
On the other hand, the Cocoa API does make frequent use of id
, because it has to. For example, consider the NSArray class, which is the object-based version of an array. In pure C, you have to declare what type of thing lives in an array; for example, you could have “an array of int.” In Objective-C, using an NSArray, you can’t do that. Every NSArray is an array of id
, meaning that each element of the array can be of any object type. You can put a specific type of object into an NSArray because any specific type of object can be assigned to an id
(id
is the universal recipient). You can get any specific type of object back out of an NSArray because an id
can be assigned to any specific type of object (id
is the universal donor).
So, for example, NSArray’s lastObject
method is defined as returning an id
. So, given an NSArray arr
, I can fetch its last element like this:
id unknown = [arr lastObject];
However, after that code, unknown
can now be sent any message at all, and we are dispensing with the compiler’s type checking. Therefore, if I happen to know what type of object an array element is, I always assign or cast it to that type. For example, let’s say I happen to know that arr
contains nothing but NSString instances (because I put them there in the first place). Then I will say:
NSString* s = [arr lastObject];
The compiler doesn’t complain, because an id
can be assigned to any specific type of object (id
is the universal donor). Moreover, from here on in, the compiler regards s
as an NSString, and uses its type checking abilities to make sure I don’t send s
any non-NSString messages, which is just what I wanted. And I didn’t lie to the compiler; at runtime, s
really is an NSString, so everything is fine.
The compiler’s type checking is called static typing, as opposed to the dynamic behavior that takes place when the program actually runs. What I’m saying here, then, is that I prefer to take advantage of static typing as much as possible.
The Cocoa API will sometimes return an id
from a method call where you might not expect it. It’s good to be conscious of this, because otherwise the compiler can mislead you into thinking you’re doing something safe when you’re not. For example, consider this code:
UIColor* c = [NSString string];
This is clearly a mistake — you’re assigning an NSString to a UIColor variable, which is likely to lead to a crash later on — but the compiler is silent. Why doesn’t the compiler warn here? It’s because the NSString string
class method is declared like this:
+ (id)string
The string
method returns an NSString, but its return value is typed as an id
. An id
can be assigned where any object type is expected, so the compiler doesn’t complain when it’s assigned to a UIColor variable. This fact is a common source of programmer mistakes (especially if the programmer is me).
Earlier, I said that it is illegal for the same class to define methods of the same type (class method or instance method) with the same name but different signatures. But I did not say what happens when two different classes declare conflicting signatures for the same method name.
This is another case in which it matters whether you’re using static or dynamic typing. If you’re using static typing — that is, the type of the object receiving the message is specified — there’s no problem, because there’s no doubt which method is being called (it’s the one in that object’s class). But if you’re using dynamic typing, where the object receiving the message is an id
, you might get a warning from the compiler; and if you’re using ARC, you’ll get a downright error: “Multiple methods named ‘rockTheCasbah’ found with mismatched result, parameter type or attributes.” This is another reason why method names are so verbose: it’s in order to make each method name unique, preventing two different classes from declaring conflicting signatures for the same method.
Accidentally defining your own method with the same name as an existing Cocoa method can cause mysterious problems. For example, in a recent online query, a programmer was confused because the compiler complained that his call to initWithObjects:
lacked a nil terminator, even though his initWithObjects:
didn’t need a nil terminator. No, his initWithObjects:
didn’t, but Cocoa’s did, and the compiler couldn’t distinguish them because this message was being sent to an id
. He should have picked a different name.
Objective-C is so dynamic that it doesn’t have to know until runtime what message to send to an object or what object to send it to. Certain important methods actually accept both pieces of information as parameters. For example, consider this method declaration from Cocoa’s NSNotificationCenter class:
- (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender
We’ll discuss later what this method does (when we talk about notifications in Chapter 11), but the important thing to understand here is that it constitutes an instruction to send a certain message to a certain object at some later, appropriate time. For example, our purpose in calling this method might be to arrange to have the message tickleMeElmo:
sent at some later, appropriate time to the object myObject
.
So let’s consider how we might actually make this method call. The object to which the message will be sent is here called notificationObserver
, and is typed as an id
(making it possible to specify any type of object to send the message to). So, for the notificationObserver
parameter, we’re going to pass myObject
. The message itself is the notificationSelector
parameter, which has a special data type, SEL (for “selector,” the technical term for a message name). The question now is how to express the message name tickleMeElmo:
.
You can’t just put tickleMeElmo:
as a bare term; that doesn’t work syntactically. You might think you could express it as an NSString, @"tickleMeElmo:"
, but surprisingly, that doesn’t work either. It turns out that the correct way to do it is like this:
@selector(tickleMeElmo:)
The term @selector()
is a directive to the compiler, telling it that what’s in parentheses is a message name. Notice that what’s in parentheses is not an NSString; it’s the bare message name. And because it is the name, it must have no spaces and must include any colons that are part of the message name.
So the rule is extremely easy: when a SEL is expected, you’ll usually pass a @selector
expression. Failure to get this syntax right, however, is a common beginner error. Notice also that this syntax is an invitation to make a typing mistake, especially because there is no checking by the compiler. If myObject
implements a tickleMeElmo:
method and I accidentally type @selector(tickleMeElmo)
, forgetting the colon or making any other mistake in specifying the message name, there is no compiler error; the problem won’t be discovered until the program runs and something bad happens. (In this case, if the tickleMeElmo
message without the colon is ever sent to myObject
, the app will probably crash with an unrecognized selector exception.)
Although your code will certainly call many Objective-C methods, it will also probably call quite a few C functions. For example, I mentioned in Chapter 1 that the usual way of initializing a CGPoint based on its x
and y
values is to call CGPointMake, which is declared like this:
CGPoint CGPointMake ( CGFloat x, CGFloat y );
Make certain that you can see at a glance that this is a C function, not an Objective-C method, and be sure you understand the difference in the calling syntax. To call an Objective-C method, you send a message to an object, in square brackets, with each argument following a colon in the method’s name; to call a C function, you use the function’s name followed by parentheses containing the arguments.
You might even have reason to write your own C functions as part of a class, instead of writing a method. A C function has lower overhead than a full-fledged method; so even though it lacks the object-oriented abilities of a method, it is sometimes useful to write one, as when some utility calculation must be called rapidly and frequently. Also, once in a while you might encounter a Cocoa method or function that requires you to supply a C function as a “callback.”
An example is the NSArray method sortedArrayUsingFunction:context:
. The first parameter is typed like this:
NSInteger (*)(id, id, void *)
That expression denotes, in the rather tricky C syntax used for these things, a pointer to a function that takes three parameters and returns an NSInteger. The three parameters of the function are an id
, an id
, and a pointer-to-void (which means any C pointer). The bare name of a function (see Chapter 1) can be used as a pointer to a C function.
So to call sortedArrayUsingFunction:context:
you’d need to write a C function that meets this description, and use its name as the first argument.
To illustrate, I’ll write a “callback” function to sort an NSArray of NSStrings on the last character of each string. (This would be an odd thing to do, but it’s only an example!) The NSInteger returned by the function has a special meaning: it indicates whether the first parameter is to be considered less than, equal to, or larger than the second. I’ll obtain it by calling the NSString compare:
method, which returns an NSInteger with that same meaning. Example 3.1 defines the function and shows how we’d call sortedArrayUsingFunction:context:
with that function as our callback (assume that arr
is an NSArray of strings).
Example 3.1. Using a pointer to a callback function
NSInteger sortByLastCharacter(id string1, id string2, void* context) { NSString* s1 = (NSString*) string1; NSString* s2 = (NSString*) string2; NSString* string1end = [s1 substringFromIndex:[s1 length] - 1]; NSString* string2end = [s2 substringFromIndex:[s2 length] - 1]; return [string1end compare:string2end]; } NSArray* arr2 = [arr sortedArrayUsingFunction:sortByLastCharacter context:nil];
Many Objective-C objects have lower-level C counterparts, along with C functions for manipulating them. For example, besides the Objective-C NSString, there is also something called a CFString; the “CF” stands for “Core Foundation,” which is a lower-level C-based API. A CFString is an opaque C struct (“opaque” means that the elements constituting this struct are kept secret, and that you should operate on a CFString only by means of appropriate functions). As with an NSString or any other object, in your code you’ll typically refer to a CFString by way of a C pointer; the pointer to a CFString has a type name, CFStringRef (a “reference to a CFString,” evidently). You work with a CFString in pure C, by calling functions.
You might, on occasion, actually have to work with a Core Foundation type even when a corresponding object type exists. For example, you might find that NSString, for all its power, fails to implement a needed piece of functionality, which is in fact available for a CFString. Luckily, an NSString (a value typed as NSString*
) and a CFString (a value typed as CFStringRef
) are interchangeable: you can use one where the other is expected, though you will have to typecast in order to quiet the worries of the compiler. The documentation describes this interchangeability by saying that NSString and CFString are “toll-free bridged” to one another.
To illustrate, I’ll use a CFString to convert an NSString representing an integer to that integer (this use of CFString is unnecessary, and is just by way of demonstrating the syntax; NSString has an intValue
method):
NSString *answer = @"42"; CFStringRef stringRef = (CFStringRef)answer; // non-ARC int ans = CFStringGetIntValue(stringRef);
The typecast prevents the compiler from complaining, and works because NSString is toll-free bridged to CFString — in effect, behind the scenes, an NSString is a CFString.
Under ARC, that code won’t compile unless you supply a little more information. ARC, as we’ll see in Chapter 12, is about memory management; but ARC manages only Objective-C objects, not their C counterparts. So although ARC manages the memory for an NSString, it leaves memory management for a CFStringRef up to you; and in order to compile that code, it needs you to show it that you understand the memory management status of this value as it crosses the toll-free bridge. You do so like this:
NSString *answer = @"42"; CFStringRef stringRef = (__bridge CFStringRef)answer; int ans = CFStringGetIntValue(stringRef);
The extra qualifier __bridge
means: “Don’t worry, ARC, I know I’m crossing the toll-free bridge, and I assure you that this has no implications for memory management.” On the other hand, there are situations where crossing the toll-free bridge does have implications for memory management, and you may rest assured that I’ll discuss them in Chapter 12.
The pointer-to-struct C data types, whose name typically ends in “Ref”, may be referred to collectively as CFTypeRef, which is actually just the generic pointer-to-void. Thus, crossing the toll-free bridge may usefully be thought of as a cast between an object pointer and a generic pointer — that is, in general terms, from id
to void*
or from void*
to id
. Even where there is no toll-free bridging between specific types (as there is with NSString and CFString), there is always bridging at the top of the hierarchy, so to speak, between NSObject (the base object class, as explained in Chapter 4) and CFTypeRef.
It is sometimes necessary to assign a CFTypeRef to an id
variable or parameter. For example, a CALayer’s setContents:
method (Chapter 16) expects an id
parameter, but the actual value must be a CGImageRef. This is legal, because a pointer is just a pointer, but the compiler will complain unless you also typecast to an id
, along with a __bridge
qualifier if you’re using ARC.
A block is an extension to the C language, introduced in Mac OS X 10.6 and available in iOS 4.0 or later. It’s a way of bundling up some code and handing off that entire bundle as an argument to a C function or Objective-C method. This is similar to what we did in Example 3.1, handing off a pointer to a function as an argument, but instead we’re handing off the code itself. The latter has some major advantages over the former, which I’ll discuss in a moment.
As an example, I’ll rewrite Example 3.1 to use a block instead of a function pointer. Instead of calling sortedArrayUsingFunction:context:
, I’ll call sortedArrayUsingComparator:
, which takes a block as its parameter. The block is typed like this:
NSComparisonResult (^)(id obj1, id obj2)
That’s similar to the syntax for specifying the type of a pointer to a function, but a caret character is used instead of an asterisk character.
So this means a block that takes two id
parameters and returns an NSComparisonResult (which is merely an NSInteger, with just the same meaning as in Example 3.1). We can define the block and hand it off as the argument to sortedArrayUsingComparator:
all in a single move, as in Example 3.2.
Example 3.2. Using a block instead of a callback function
NSArray* arr2 = [arr sortedArrayUsingComparator: ^(id obj1, id obj2) { NSString* s1 = (NSString*) obj1; NSString* s2 = (NSString*) obj2; NSString* string1end = [s1 substringFromIndex:[s1 length] - 1]; NSString* string2end = [s2 substringFromIndex:[s2 length] - 1]; return [string1end compare:string2end]; }];
The syntax of the inline block definition is:
^❶(id obj1, id obj2)❷ {❸
First, the caret character. |
|
Then, parentheses containing the parameters. |
|
Finally, curly braces containing the block’s content. |
Thanks to the block, as you can see, we’ve combined the definition of the callback function with its use. You might object that this means the callback isn’t reusable; if we had two calls to sortedArrayUsingComparator:
using the same callback, we’d have to write out the callback in full twice. To avoid such repetition, or simply for clarity, a block can be assigned to a variable:
NSComparisonResult (^sortByLastCharacter)(id, id) = ^(id obj1, id obj2) { NSString* s1 = (NSString*) obj1; NSString* s2 = (NSString*) obj2; NSString* string1end = [s1 substringFromIndex:[s1 length] - 1]; NSString* string2end = [s2 substringFromIndex:[s2 length] - 1]; return [string1end compare:string2end]; }; NSArray* arr2 = [arr sortedArrayUsingComparator: sortByLastCharacter]; NSArray* arr4 = [arr3 sortedArrayUsingComparator: sortByLastCharacter];
The return type in an inline block definition is usually omitted. If included, it follows the caret character, not in parentheses. If omitted, you may have to use typecasting in the return
line to make the returned type match the expected type. For a complete technical syntax specification for blocks, see http://clang.llvm.org/docs/BlockLanguageSpec.html.
The power of blocks really starts to emerge when they are used instead of a selector name. In an example earlier in this chapter, we talked about how you could pass @selector(tickleMeElmo:)
as the second argument to addObserver:selector:name:object:
as a way of saying, “When the time comes, please call my tickleMeElmo:
method.” We also talked about how error-prone this syntax was: make a typing error, and your tickleMeElmo:
method mysteriously won’t be called. Moreover, such code is hard to maintain; there’s the tickleMeElmo:
method sitting there, completely separate from the code that calls addObserver:selector:name:object:
, yet existing only to specify what should happen at the later time when our message arrives. It might well be clearer and more compact to call addObserverForName:object:queue:usingBlock:
and specify there and then as a block what should happen at message time, with no separate method callback. (I’ll talk about this again, along with an example, in Chapter 11.)
Perhaps the most remarkable feature of blocks is this: variables in scope at the point where a block is defined keep their value within the block at that moment, even though the block may be executed at some later moment. (Technically, we say that a block is a closure.) It is this aspect of blocks that makes them useful for specifying functionality to be executed at some later time, or even, as we’ll see in Chapter 38, in some other thread.
Here’s an example that will appear in Chapter 17. It will make perfect sense to you in its proper context, so I won’t explain it fully now; but the point is that outside any blocks we have a UIView object v
in scope, along with a CGPoint p
and another CGPoint pOrig
, and we can use the two CGPoint values to mutate v
inside two blocks (called anim
and after
), even though these blocks won’t be executed until some indeterminate moment in the future, at the start and end of an animation:
CGPoint p = v.center; CGPoint pOrig = p; p.x += 100; void (^anim) (void) = ^{ v.center = p; }; void (^after) (BOOL) = ^(BOOL f) { v.center = pOrig; }; NSUInteger opts = UIViewAnimationOptionAutoreverse; [UIView animateWithDuration:1 delay:0 options:opts animations:anim completion:after];
If a variable outside a block is in scope within the block, and if that variable is an object reference,
messages can be sent to it and the object may be mutated, as we did with the UIView object v
in that example. But if we try, inside a block, to assign directly to a variable outside the block, we can’t do it; the variable is protected, and the compiler will stop us (“variable is not assignable”):
CGPoint p; void (^aBlock) (void) = ^{ p = CGPointMake(1,2); // error };
On rare occasions, you may need to turn off this protection; you can do so by declaring the variable using the __block
qualifier. Here’s an example that will appear in Chapter 35. We cycle through an array until we find the value we want; when we find it, we set a variable (dir
) to that value. That variable is declared outside the block, because we intend to use its value after executing the block; therefore we qualify the variable’s declaration with __block
, so that we can assign to it from inside the block:
CGFloat h = newHeading.magneticHeading; __block NSString* dir = @"N"; NSArray* cards = @[@"N", @"NE", @"E", @"SE", @"S", @"SW", @"W", @"NW"]; [cards enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if (h < 45.0/2.0 + 45*idx) { dir = obj; *stop = YES; } }]; // now we can use dir
(Note also the assignment to a dereferenced pointer-to-BOOL. When the method to which we are submitting a block is going to call the block repeatedly as the equivalent of a for loop, we can’t abort the loop with a break
statement, because this isn’t a real for loop. So the method will commonly specify that our block should take a pointer-to-BOOL parameter; the idea is that we can set this BOOL by indirection to YES, and the method will notice this as it prepares to call the block for the next iteration, and will stop instead. This is one of the few common situations in iOS programming where it is necessary to dereference a pointer.)
Another use of the __block
qualifier is to allow a block to capture the value of a variable that is set by the very same method call that takes the block as an argument. Here’s an example that will appear in Chapter 38:
__block UIBackgroundTaskIdentifier bti = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{ [[UIApplication sharedApplication] endBackgroundTask:bti]; }];
The method beginBackgroundTaskWithExpirationHandler:
takes a block and returns a UIBackgroundTaskIdentifier
, which is really just an integer. We want to use that integer inside the block, which will actually be executed at some later time (if ever). If we don’t declare the integer variable with the __block
qualifier, the block will capture the variable’s value at the time the block is defined, which is before the beginBackgroundTaskWithExpirationHandler:
method call is actually executed. After the method call is executed, the variable is set to its true value, the value we want to use inside the block; because we declared the variable with __block
, the block has access to that true value.
Note that this trick works only because the block is being stored (by the receiver of the beginBackgroundTaskWithExpirationHandler:
message) for later execution. If the block were to be executed right now, before returning from the beginBackgroundTaskWithExpirationHandler:
call, the result of that call would not yet have been set.
At the same time that blocks were introduced into Objective-C, Apple introduced a system library of C functions called Grand Central Dispatch (GCD) that makes heavy use of them. GCD’s most important use is for threading (Chapter 38), but it also comes in handy for expressing neatly and compactly certain notions about when code should be executed. For example, GCD can help us delay execution of our code (delayed performance). The following code (from Chapter 14) means, “change the bounds of v1
, but not right this moment — wait two seconds and then do it”:
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ CGRect f = v1.bounds; f.size.width += 40; f.size.height -= 50; v1.bounds = f; });
This next example rewrites the code from the end of Chapter 1, where a class method vends a singleton object. GCD promises that the block creating the singleton object to begin with will execute only once in the entire life of our program, thus guaranteeing that the singleton is a singleton:
+ (CardPainter*) sharedPainter { static CardPainter* sp = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sp = [CardPainter new]; }); return sp; }
Why is calling dispatch_once
better, as a way of making sure we generate the singleton instance only once, than testing sp
against nil, as in Chapter 1? Aside from being thread-safe, it isn’t; it’s just an example of GCD’s elegant use of a block.