The id Type and Protocols
Editor’s note: I recently bumped into a bit of Objective-C syntax that was unfamiliar to me. The code was in this form:
id<SomeProtocol> variableName = [someObject someMessage]
I’d never seen a protocol paired with an id type. I asked Paul Cantrell about it and he prepared this:
The general form of an Objective-C type is:
Class<Protocol, Protocol, Protocol> *
The type is a set of constraints, or promises, about an object. There may be zero, one, or many protocols, but there’s always exactly one object type specifier.
Because of its Smalltalk heritage, and because it doesn’t have parametric types, Objective-C makes its static type checking optional via the special type “id”. It’s not simply a shortcut for NSObject* or even void*; it means “disable all static Obj-C type checking on this expression.” So, for example, if the NSArray method were:
- (NSObject*) objectAtIndex: (NSUInteger) index
…then you would need to do a cast:
Thinger *foo = (Thinger*) [array objectAtIndex:0];
However, because it’s declared as id:
- (id) objectAtIndex: (NSUInteger) index
…no cast is necessary:
Thinger *foo = [array objectAtIndex:0];
You can declare everything to be id and poof! you have a duck-typed language. Well, almost: any messages you send need to exist *somewhere* on some type, or you’ll get a warning.
Special things happen if you mix id with protocols. This compiles with no errors or warnings:
id foo;
...
[foo length];
However, the presence of a protocol signals to the compiler that you have at least some idea what kind of object you have, and type checking suddenly awakens. This gives a warning ‘-length not found in protocol(s)’:
id<UIScrollViewDelegate> foo;
...
[foo length];
The two things that always bite me:
(1) id doesn’t support properties
Oddly, the compiler presumed that expressions of type id support *all*
messages but *no* properties. This fails to compile:
[array objectAtIndex:0].text = @"hello";
… with the sublimely unhelpful (and grammatically incorrect) compiler message, “confused by earlier errors, bailing out.” You will also sometimes see “request for member ‘foo’ in ‘bar’, which is of non-class type ‘objc_object*'” in variants of this situation.
The solution is to give the id an explicit type. This works:
UILabel *label = [array objectAtIndex:0];
label.text = @"hello";
(2) id doesn’t include an *
This gives a bunch of really confusing “invalid receiver type” errors, because of the
misplaced *:
id<MyThingerDelegate> *delegate;
Great post! So far I’ve generally shied away from explicitly using the “id” type in my own code, but I’ve definitely come across some of the confusing cases you point out when calling existing methods. Thanks for clearing those up!
I recommend reading Apple’s documentation on formal protocols in Objective-C.
-jcr