Monday, April 13, 2009

Objective-C basics for C programmers

Objective-C is C

First, if you want to learn Objective-C and you don’t know C yet, go learn C.  Come back when you’re done.  Objective-C is a set of addons to C – you must know C first.  C is a small language, and it won’t take you that long.  (Learning to use C well is a different problem, of course.)

What to read

The Apple doc for Objective-C is pretty good, but it gets bogged down in places because it's not assuming you already know C.  It's also written as if Objective-C were a new language, instead of just some extra bits on top of C.

(And Chapter 11 of that Apple doc tosses in this shocker: C++ is also included in Objective-C.  It's not usually necessary to use C++ on the iPhone, but it does mean you can use lots of existing C++ code if you need it.)

This article is a stripped-down intro to thinking of Objective-C basics in terms of what they are in C.

Square braces are just macros

The best way to think about Objective-C syntax is that it’s just a new flavor of preprocessor for C.  There are some basic things you care about that are going to spit out normal C, eventually:
  • Messages.  They’re sent using square braces like this: [targetObject theMessage]
  • Class definitions.  They’re blocks that start with @interface @implementation @protocol and end with @end.
  • Selectors.  Selector is the Objective-C term for what most languages call “messages.”  They’re what you send to objects.  (Thinking of them as method invocations is mostly wrong and will get you in trouble later on.)  You’ll see selectors used like this: @selector(theMessage) and in square braces like this: [targetObject theMessage] - that’s just syntatic sugar.  They’re going to compile down to the same thing.

Classes

Classes are structs with some extra fields tacked on.  Here's a trivial class declaration:

@interface SmallClass : NSObject
- (void) helloWorld;
@end

This lays out the class for the compiler.  It allows the compiler to give you useful warnings if you try to send the helloWorld message to this class in the wrong way (so you'll get a warning if you try to call it with a parameter, for example).  It adds a field that's a pointer to the class itself, so at runtime you can dereference the pointer to the class type to figure out which functions correspond to which selectors.   (And yes, this implies that at runtime a class can change its type.  That's nasty, and is used by things like key-value observing.)

The implementation for this would be:

@implementation SmallClass
- (void) helloWorld
{
  NSLog(@"Hello world.");
}
@end
     
The compiler turns this into a helloWorld function that looks something like:

void helloWorldFunctionImplementation (SmallClass *self) {
  NSLog(@"Hello world");
}

To create an instance of SmallClass and send it the helloWorld selector, you'd use the the square brackets syntax:

SmallClass *sm = [[[SmallClass alloc] init] autorelease];
[sm helloWorld];


(For now, ignore the alloc/init/autorelease bits - they're memory management on the iPhone.)

Square brackets are just syntatic sugar that makes sending selectors to objects more pleasant.  What you end up with after square-bracket-procesing is a call to the function objc_msgSend(receiver, selector).  Calling objc_msgSend without the square brackets would do exactly the same thing:

objc_msgSend(sm, @selector(helloWorld));  // same as [sm helloWorld]

Or the same thing in a function:

void sendHelloWorldByHand (SmallClass *target)
{
  objc_msgSend(target, @selector(helloWorld));
}

Notice that the call to @selector(helloWorld) couldn't care less about whether or not there's a method called helloWorld implemented by that object.  It's completely irrelevant - selectors just get translated to numbers by the compiler, and they're not tied to classes in any way.  @selector(helloWorld) becomes an int inside the compiler, and you can send that int to any instance of any class.  Whether or not the target does anything interesting with that message is an unrelated issue.

To put it another way: selectors aren't tied to particular objects; the selector for helloWorld on SmallClass is the same selector that you might send to AnotherClassIHaventImplementedYet.  Selectors are just a lookup mechanism so humans don't have to remember numbers - they get informative tokens to put in code.  But they're just going to turn into numbers when your code is running.

(And you should notice that the Apple doc says not to call objc_msgSend by hand, although I suspect this is widely ignored.)

Sending messages

objc_msgSend(target, selector) goes through these steps to figure out what to call:
  1. Looking at the hidden field in the struct that points to the class of the object. 
  2. Call the function matching the selector.  Classes have lookup tables that specify the selector-to-function mapping, and if there's a matching selector, that's what's called.  If there's no matching selector, check the parent of the class, and so on until you get to the root class. 
  3. If there's no match for the selector, call forwardInvocation:.
#3 is important – it means that you cannot tell by looking at an object's @interface code whether or not it handles a selector.  That decision can't really be made until you're actually sending the selector to the object.  (And since forwardInvocation: is free to do anything it feels like with a selector, there's no guarantee that a selector will do anything the same way twice.)

No comments: