Swift & Objective-C

Mastering Objective-C id (Dynamic and Duck Typing Summary)

When you first study Objective-C, the id type can make you pause.

4 min read
Cover image for Mastering Objective-C id (Dynamic and Duck Typing Summary)

When you first study Objective-C, the id type can make you pause.

You may wonder, “What kind of type is this? Why does it compile when I put any object in it?”

In short, id is a “universal pointer that can refer to any object,” and it is the foundation of Objective-C’s dynamic and duck typing.

This article explains exactly what the id type is, how dynamic and duck typing work in real code, and when to use or treat them with caution.

What exactly is the id type?

In a word, id is a “pointer to an arbitrary Objective-C object.”

You can store any object without specifying a concrete class such as NSString * or NSArray *.

The truth is that id is simpler than it seems. Internally, it is defined like this.

// objc.h Internal definition (summary)
typedef struct objc_object {
    Class isa;  // A pointer indicating which class this object belongs to
} *id;

In other words, id is a pointer to a struct containing one pointer, isa.

What id really is: a pointer containing just isa
What id really is: a pointer containing just isa

This isa acts as a label that tells the runtime, “I am an instance of this class.”

Here is a subtle point: id is already a pointer, so you write id obj, without adding an asterisk as in id *obj.

This is easy to confuse with NSString *str.


How dynamic typing works

Dynamic typing means that “the object’s actual type is determined at runtime, not at compile time.”

The class of the object stored in id is determined while the program runs by examining the isa pointer.

That makes code like this possible.

id obj = @"Hello";           // Currently NSString
NSLog(@"%@", [obj class]);   // Output: __NSCFConstantString
obj = @[@1, @2, @3];         // Now changed to NSArray
NSLog(@"%@", [obj class]);   // Output: __NSArrayI

The same variable, obj, changes from a string to an array.

The compiler does not prevent this because it defers determining the actual type until runtime.

The same applies when sending messages. When you call [obj length], the compiler cannot know whether obj will respond to length.

At that moment, the runtime searches obj’s class for the method and executes it. This is called message dispatch.

Here is the comparison.

  • Static typing: the compiler determines and checks types in advance
  • Dynamic typing: the actual object is inspected and the decision is made during execution

In Objective-C, id and dynamic typing embody the philosophy: “put it in first, then ask what it really is at runtime.”


What does duck typing really mean?

This is where many people misunderstand the concept. They reduce duck typing to “not checking types.”

The original expression behind duck typing is:

“If it walks like a duck and quacks like a duck, it’s a duck.”

The important question is not which class an object belongs to, but whether it can respond to “that message.”

In Objective-C, you can check this directly with respondsToSelector:.

// Regardless of the class, call it if it responds to this method
if ([obj respondsToSelector:@selector(quack)]) {
    [obj quack];  // Treat it as a duck if it can quack like one
}

It does not matter whether obj belongs to the Duck class or the Robot class.

As long as it responds to the quack message, our code treats it as a “duck.”

This differs from inheritance-based polymorphism.

Classes do not need to share a parent; even unrelated classes can be handled identically as long as they have the same method name.

This flexibility is the foundation for core Cocoa designs such as the delegate pattern and target-action.

It is a fun idea: respond to the method, and you count as a duck
It is a fun idea: respond to the method, and you count as a duck

The convenient but dangerous id: when should you be careful?

id comes with a price for its freedom.

Because the compiler performs little type checking, sending an unsupported message passes compilation but crashes the app at runtime.

That is the infamous unrecognized selector sent to instance crash.

In practice, use it like this.

  • When the type is known, specify a concrete class such as NSString * instead of id
  • If you must use id, guard the call with respondsToSelector:
  • Define a delegate protocol to document “responds to this message.”

These days, instancetype and generics such as NSArray<NSString *> * have reduced indiscriminate use of id.

Even so, id still beats like a heart beneath frameworks and in runtime code.

This red log is the face you will encounter most often when misusing id
This red log is the face you will encounter most often when misusing id

To summarize, id represents Objective-C’s dynamic nature, while dynamic and duck typing share the philosophy that “objects are judged by behavior, not class.”

It may seem unfamiliar and dangerous at first, but once you understand the principle, Cocoa framework designs become much clearer.

I hope id feels a little less daunting now. Enjoy studying Objective-C!