Subscribe to get more articles about programming languages
Donate

Objective-C id type

So what’s “id” exactly? Is it like a void* pointer from C, or is it like a root object of the OO hierarchy like NSObject?

black box

Basics

“id” is a data type of object identifiers in Objective-C, which can be use for an object of any type no matter what class does it have. “id” is the final supertype of all objects. In the simplest form this statement:

id x;

declares a variable “x” as having a data type “id”.

Let’s look on the definition of “id” in the header file “objc/objc.h”.

typedef struct objc_object *id;

It’s a pointer to struct. Although being a pointer type, it’s a pointer that’s treated by the Objective C compiler in a very special way. “id” brings dynamic typing into otherwise a static typed language. What it means in practice is that the type of an object it points to is not checked by the compiler. This is similar to languages like Ruby, Python or JavaScript. If used with care this language feature can give great flexibility.

Examples

First of all, it is possible to treat any object pointer type as “id” without an explicit conversion. In this sense it is quite similar to the root base class type or a void* pointer:

NSString *name = @"dobegin";
id data = name;

One thing you can do after that is to do an implicit cast from “id” type to any other object type:

id data = ...;
NSString *name = data;

See how there’s no cast involved to convert the value of type “id” to the string value? For example in Java or C# you would do something like:

Object data = ...;
String name = (Object)data;

This sort of “downcasts” is usually considered flawed or malicious by many strongly-typed languages, because the compiler can’t do careful type checking here and it’s impossible to say if the type on the left side is correct until you run the program. For example in C++ one uses “reinterpret_cast” for such a case:

void *data = ...;
string *name = reinterpret_cast<string>(data);

This is known to be dangerous.

Consider this example, which is a totally valid Objective-C code:

NSString *name = @"dobegin";
id data = name;
NSURL *site = data;
NSString *host = site.host; // oops! runtime error! 
							// "unrecognized selector sent to instance"

The next unique thing you can do with “id” is calling an arbitrary method on an “id” object like this:

id data = ...;
NSString *desc = [data description];

Thus “id” type is polymorphic. This is very powerful as it doesn’t require a cast to use such a value:

id data = ...;
NSString *host = [data host]; // assume NSURL

Sure enough “id” usage can lead to bugs, because the availability of the method is determined at runtime. This is called “dynamic (or late) binding”. Nevertheless if used carefully it is very useful and lets you write parts of the code faster as if using a scripting language.

Unfortunately this doesn’t work with property syntax:

id data = ...;
NSString *host = data.host; // error: "Property 'host' not found"
data.host = @"dobegin.com"; // same error

Working with generic functions, callbacks or delegate methods you might have to write code for a function that takes “id” object as a parameter:

NSArray *pocketMoney = @[@2, @5, @50, @1, @100];
NSPredicate *coinFilter = [NSPredicate predicateWithBlock:^BOOL(
								id moneyItem, NSDictionary *bindings) {
    return [moneyItem intValue] < 10; // assume NSNumber
}];
NSArray *coins = [pocketMoney filteredArrayUsingPredicate:coinFilter];

In many cases you know what exact argument type to expect, and then it’s actually better to add additional type safety by specifying that type like this:

NSArray *pocketMoney = @[@2, @5, @50, @1, @100];
NSPredicate *coinFilter = [NSPredicate predicateWithBlock:^BOOL(
								NSNumber *moneyItem, NSDictionary *bindings) {
    return [moneyItem intValue] < 10;
}];
NSArray *coins = [pocketMoney filteredArrayUsingPredicate:coinFilter];

This might seem like a contract violation, but in practice Objective C compiler does not distinguish “id” from any other object pointer type. The crucial difference here is that the code inside the block is type-checked right now and you can’t call a non-existing method on moneyItem by accident.

Untyped collections

Before 2015 all collection types such as NSArray, NSDictionary, NSSet were based on elements typed with “id”. Most of the legacy code is written in a type-unsafe way similar to Java before version 1.5 (from 2004) or C# and Visual Basic before .NET 2.0 (from 2006) when generics and type-safe collections were introduced.

In the past without generics support you could make a collection like NSArray to be type-safe by creating a wrapper class around it, declaring all required methods with a specific type and forwarding a typed parameter to as an “id” to the collection:

@implementation MYNoteList { // a class that wraps an array of notes
	NSMutableArray *_notes;  // private field
}
- (void)addNote:(MYNote *note) { [_notes addObject:note]; }
@end

This approach needs boilerplate code, so in practice you see lots of raw NSArray-based code.

With the introduction of generics this problem should partially go away, but there are still cases where you have to deal with “id” in collections, for example:

  • JSON deserialization: for example, a JSON NSDictionary might have values of different types including NSArray, NSString or NSDictionary itself.
  • Collections with NSNull elements: for example, since NSArray is not able to store nil-values, people sometimes use a special object [NSNull null] of type NSNull for this. It means that your values might be one of two types: a normal element type or NSNull.

Misc.

Default return type

“id” is a default return type of object or class methods, which don’t specify a type:

@interface Pizza : NSObject
@end
@implementation Pizza
+ createNew { return [Pizza new]; }
@end
// call like this:
// Pizza *p = [Pizza createNew];

More typical usage would be to avoid this shortcut and specify a precise type like this:

+ (Pizza *)createNew { return [Pizza new]; }

There’s a post explaining why is it like that. In short, historically in the first Objective-C versions (inspired by Smalltalk programming language) “id” was the only type used for all objects.

instancetype

In short, in the past all constructors would have an “id” return type like this:

- (id)init { ... }

while in modern Objective-C those should be changed to:

- (instancetype)init { ... }

See an article about instancetype for a longer detailed explanation.

id<MYProtocolType>

id<T> defines a type of some object that implements (“conforms to”) a protocol “T”. This type behaves much more like a normal type. You can’t assign any object to a variable of type id<T> and you can’t cast it back implicitly even if the destination object is compatible with “T”. It is an error to call an arbitrary method on such a variable.

C# dynamic

C# 4.0 has added a similar feature with its “dynamic” keyword. Not only does it support object types, but also primitive types. On the other hand, you have to do an explicit cast when you want a concrete type (like C++ example above):

dynamic stranger = GetUser();
stranger.Register();		// method or property access
stranger.Name = "dobegin";  // without compiler type-checking
User user = (User)stranger;

See also

Subscribe to get more articles about programming languages
Donate