I’ve mentioned several times that your variables referring to Objective-C objects are going to be pointers:
NSString* s = @"Hello, world!";
Although it is common to speak loosely of
s as an NSString (or just as a string), it is actually an
NSString* — a pointer to an NSString. Therefore, when a C function or an Objective-C method expects an
NSString* parameter, there’s no problem, because that’s exactly what you’ve got. For example, one way to concatenate two NSStrings is to call the NSString method
stringByAppendingString: (that’s not a misprint; the colon is part of the name), which the documentation tells you is declared as follows:
- (NSString *)stringByAppendingString:(NSString *)aString
The space between the class name and the asterisk is optional, so this declaration is telling you (after you allow for the Objective-C syntax) that this method expects one
NSString* parameter and returns an
NSString*. That’s splendid because those kinds of pointers are just what you’ve got and just what you want. So this code would be legal:
NSString* s1 = @"Hello, "; NSString* s2 = @"World!" NSString* s3 = [s1 stringByAppendingString: s2];
The idea, then, is that although Objective-C is chock-a-block with pointers and asterisks, they don’t make things more complicated, as long as you remember that they are pointers.
Sometimes, however, a function expects as a parameter a pointer to something, but what you’ve got is not a pointer but the thing itself. Thus, you need a way to create a pointer to that thing. The solution is the address operator (K&R 5.1), which is an ampersand before the name of the thing.
For example, there’s an NSString method for reading from a file into an NSString, which is declared like this:
+ (id)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error
Now, never mind what an
id is, and don’t worry about the Objective-C method declaration syntax. Just consider the types of the parameters. The first one is an
NSString*; that’s no problem, as every reference to an NSString is actually a pointer to an NSString. An NSStringEncoding turns out to be merely an alias to a primitive data type, an NSUInteger, so that’s no problem either. But what on earth is an
By all logic, it looks like an
NSError** should be a pointer to a pointer to an NSError. And that’s exactly what it is. This method is asking to be passed a pointer to a pointer to an NSError. Well, it’s easy to declare a pointer to an NSError:
But how can we obtain a pointer to that? With the address operator! So our code might look, schematically, like this:
NSString* myPath = // something or other; NSStringEncoding myEnc = // something or other; NSError* myError = nil; NSString* result = [NSString stringWithContentsOfFile: myPath encoding: myEnc error: &myError];
The important thing to notice is the ampersand. Because
myError is a pointer to an NSError,
&myError is a pointer to a pointer to an NSError, which is just what we’re expected to provide. Thus, everything goes swimmingly.
This device lets Cocoa effectively return two results from this method call. It returns a real result, which we have captured by assigning it to the NSString pointer we’re calling
result. But if there’s an error, it also wants to set the value of another object, an NSError object; the idea is that you can then study that NSError object to find out what went wrong. (Perhaps the file wasn’t where you said it was, or it wasn’t stored in the encoding you claimed it was.) By passing a pointer to a pointer to an NSError, you give the method free rein to do that. Before the call to
myError was uninitialized; during the call to
stringWithContentsOfFile:, Cocoa can, if it likes, repoint the pointer, thus giving
myError an actual value.
So the idea is that you first check
result to see whether it’s nil. If it isn’t, fine; it’s the string you asked for. If it is, you then study the NSError that
myError is now pointing to, to learn what went wrong. This pattern is frequently used in Cocoa.
You can use the address operator to create a pointer to any named variable. A C function is technically a kind of named variable, so you can even create a pointer to a function! This is an example of when you’d use the name of the function without the parentheses: you aren’t calling the function, you’re talking about it. For example,
&square is a pointer to the
square function. In Chapter 9, I describe a situation in which this is a useful thing to do.
Another operator used in connection with pointers, or when memory must be allocated dynamically, is
sizeof. It may be followed by a type name in parentheses or by a variable name; a variable name needn’t be in parentheses, but it can be, so most programmers ignore the distinction and use parentheses routinely, as if
sizeof were a function.
For example, the documentation shows the declaration for
AudioSessionSetProperty like this:
OSStatus AudioSessionSetProperty ( AudioSessionPropertyID inID, UInt32 inDataSize, const void *inData );
Never mind what an AudioSessionPropertyID is; it’s merely a value that you obtain and pass on. UInt32 is one of those derived numeric types I mentioned earlier. The discussion has already dealt with pointer-to-void and how to derive a pointer using the address operator. But look at the name of the second parameter; the function is asking for the size of the thing pointed to by the third parameter. Here’s an actual call to this function (from Chapter 27):
UInt32 ambi = kAudioSessionCategory_AmbientSound; AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(ambi), &ambi);