2014-05-19

A few weeks ago, I stumbled upon these private methods on NSObject:

@interface NSObject (Private)
-(id)_ivarDescription;
-(id)_shortMethodDescription;
-(id)_methodDescription;
@end

They can be used to obtain debug descriptions of the passed objects:

  • _shortMethodDescription lists all the instance and class methods of the receiver,
  • _methodDescription does the same, including the superclasses’ methods,
  • _ivarDescription lists all the instance variables of the receiver, their type, and their value.

The underlying mechanism is runtime introspection, similar to what Nick Lockwood described in this post on iosdevelopertips.com. The good news is Apple already did the hard work for us. The bad news, of course, is that these methods are “private”.

Here’s an example1 of what it does:

@implementation MyClass : NSObject
{
    NSString* myIVar;
}

- initWithString:string
{
    self = [super init];
    if(self) {
        myIVar = string;
    }
    return self;
}
@end

void main()
{
    id obj = [[MyClass alloc] initWithString:@"Hello"];
    NSLog(@"%@",[obj _ivarDescription])
}
ivars: <MyClass: 0x8d3d130>:
in MyClass:
    myIVar (NSString*): @"Hello"
in NSObject:
    isa (Class): MyClass
methods: <MyClass: 0x8d3d130>:
in MyClass:
    Instance Methods:
        - (void) .cxx_destruct; (0x2a30)
        - (id) initWithString:(id)arg1; (0x2930)
(NSObject ...)

These are really great debugging tools, and I’ve started to use them instead of -description or -debugDescription. In fact, I’d love the default implementation of -debugDescription to use -_ivarDescription.

(A word of warning, however: these methods are in fact implemented in UIKit on iOS7, and not available at all on Mac OS. Yet.)

The class of the nil object

Here’s the really cool trick: _ivarDescription knows the class of nil object ivars. If we change our example to this:

id obj = [[MyClass alloc] initWithString:nil];

-_ivarDescription still prints out the myIVar iVar as type "NSString*", with a nil value:

ivars: <MyClass: 0x8e40960>:
in MyClass:
    myIVar (NSString*): nil
in NSObject:
    isa (Class): MyClass

What? Nil objects don’t have a class; they’re just nil pointers. _ivarDescription prints the expected class of the instance variable, as specified in the source code.

In fact, the compiler stores extended type info in the binary. That’s really weird: Objective-C treats all objects equals, as ids, and will not complain when assigning an object of a class to a pointer of a different type. I always assumed that class info was lost at runtime; it turns out that’s not true.

Import the Runtime!

Let’s try to reimplement _ivarDescription and see what the runtime has to offer. First, class_getInstanceVariable() lets us get a object’s Ivar given its name, then ivar_getTypeEncoding() will return its type encoding:

> p (char*) ivar_getTypeEncoding(class_getInstanceVariable([MyClass class], "myIVar"))

@"NSString"

Now this is big news to me. According the documentation, the type encoding for objects is simply @. In reality, it’s followed by the actual expected class.

Let’s try it again, with a declared property:

@property NSString * myproperty;

This time we’ll print out the complete attributes for the property:

> p (char*) property_getAttributes(class_getProperty([MyClass class], "myproperty"))

T@"NSString",&,V_myproperty

Again, we find the expected class, in addition to the property-specifc attributes: the storage (weak or strong) and the ivar name.

opensource.apple.com

Digging a little further in the objc4 runtime source code, here’s another surprise: Protocol methods extended type info.

As with ivars, methods arguments expect objects of a specific class, and this information is embedded in the binary. For some reason, it’s only available for methods declared in Protocols, and the public runtime API won’t let us access it, but the private function _protocol_getMethodTypeEncoding() will do.

Here it is in action:

@protocol FooProtocol
- (NSString *) barWithArray:(NSArray*)array; 
@end
> p (char*) _protocol_getMethodTypeEncoding(@protocol(FooProtocol), @selector(barWithArray:), YES, YES)

@"NSString"12@0:4@"NSArray"8

We get extended type info for the parameter and the return type of the method. This is much more than what the public function method_getTypeEncoding() returns:

@implementation FooClass
- (NSString *) bazWithArray:(NSArray*)array_; 
@end
> p (char*) method_getTypeEncoding(class_getInstanceMethod([FooClass class], @selector(bazWithArray:))

@12@0:4@8

In fact, according to the docs, the encoding for this method should be @@:@:

  • '@' for “object” return type
  • '@' for “object” receiver (self)
  • ':' for “selector” first parameter (the _cmd hidden parameter)
  • '@' for the NSArray parameter.

(As far as I know, the numeric values in @12@0:4@8 are not documented; I can only assume these are hints for byte alignment.)

To sum up:
Clang and the objc runtime have partial, undocumented support for Extended Type Info, both for the instance variables and the protocol methods. For some reason, it’s not implemented for regular (non-protocol) methods, what is implemented is private API, and all of it is completely undocumented.

More of this please

It’s a bit complicated to understand the changes in progress in the objective-C runtime, given the lack of regular opensource releases. Extended Type Info in Objective-C currently looks like a work in progress, a partially implemented experiment.

On the other hand, and this is pure speculation, but if this announces a new, more powerful introspection mechanisms, I’m all for it.

  1. Following the minimalist codestyle of the latest post