Objective-C for Java Programmers, Part 2
- Methods and Messages
- Interfaces and Implementations
- No Magic
- Memory Management
- Exceptions and Errors
- Overall
Last week, we looked at some of the major semantic differences between Objective-C and Java. This week, we're going to get a bit closer to some of the Objective-C syntax and see exactly how you go from programming in Java to Objective-C.
Methods and Messages
Objective-C makes a distinction between sending a message and calling a method. When you send a message to an Objective-C object, two things happen. First, the runtime looks up the function used to implement the corresponding method, then it calls the function. The VM does something similar when you call a Java method.
The big difference happens when the object does not implement the method. In Java, you get a runtime exception (unless you are using the Proxy classes from Java 1.3). In Objective-C, first the runtime will call the -forwardingTargetForSelector: method. This may optionally be implemented by any object and returns another object. The message will then be sent to that object instead of the original receiver.
If this method isn't implemented, or returns nil, then the method is wrapped up in an NSInvocation object and delivered to the -forwardInvocation: method. The default implementation of this throws an exception (the same behavior that you get in Java), but you can override it to do something different. Between these, there are a few other hooks that may be called, allowing the receiver to add methods to the class and then retry.
The invocation object encapsulates the target, the selector, and all of the arguments of the message send in an object. You can inspect or modify any of these and then invoke it, or pass it off to a different thread. In Etoile, we use this in a couple of interesting ways. The EtoileThread framework lets you put an object in another thread and transparently forward message to it. These are added to a queue and executed in order in the other thread. The return value is another proxy, which blocks when it receives a message until the original message has completed and the return value been set. In CoreObject, we record every message sent to a model object (on disk, or we send it over the network) so that you can replay the entire history of an object.
So what does a message send look like? In Java, a message call looks like a call to a function pointer in a C structure. In Objective-C, this syntax deliberately wasn't used to make it clear when you were calling C functions and Objective-C methods. Instead, Objective-C uses Smalltalk syntax for message sends. This can look quite intimidating at first. Here are some examples:
[socket close]; [array objectAtIndex: 12]; [dictionary setObject: @"some value" forKey: @"a key"];
The first of these is a message that takes no arguments. The second takes one argument, and the third takes two. Notice in the last case that all arguments have names. This prevents ambiguity. Without named arguments, it would not be immediately obvious to someone reading the code for the last example which was the value and which the key being inserted into the dictionary.
When you declare a method, you use syntax a like this:
- (void)setObject: (id)anObject forKey: (id)aKey;
Note the bits in brackets that look like cast expressions. The reason that they look like cast expressions is that they are. This is a subtle and important difference between Objective-C methods and C functions. The Objective-C runtime stores function pointers for all of the methods. These are typed as C functions with this signature:
id method(id self, SEL _cmd, ...);
When you call a method, the function will be looked up and then cast to whatever method signature the compiler thinks it should have, then called. If you're using the GNU runtime, that's exactly what happens in the generated code. With the Apple runtime, there is some horrible assembly code that does something equivalent but slightly faster.
Most of the time you can ignore this. It's sometimes useful, however. You can ask the runtime (directly or via NSObject) for this function pointer and call it directly. This is faster because it avoids the dynamic lookup, but will sometimes break if you're not careful.