MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


Inside the Objective-C Runtime, Part Two

by Ezra Epstein
05/31/2002

Editor's note -- In his previous article, Ezra Epstein showed how to access the basic features of the Objective-C runtime. In this follow-up article, he digs a little deeper and looks at how the runtime is implemented. This digging results in a better understanding of Categories, and it also reveals details that may not be present in header (.h) files. The culmination of this brief foray is a look at RuntimeBrowser, a class-browsing tool like the JavaBrowser that the author has found quite useful and thinks you might too.

Digging Deeper Into Runtime

The Objective-C runtime is written in C. In fact, the original version of Objective-C was implemented as a C compiler pre-processor. To get "inside" the Objective-C runtime we use C functions to access C data structures. The central C struct is struct objc_class* (a.k.a., a Class).

To get the struct and function definitions we'll include the objc header files. They should already be on your system in: /usr/include/objc (or System/Developer/Headers/objc). You don't need Mac OS X, however. The source code and the headers for the runtime are available as part of the Darwin open source project: objc4-217.tar.gz. (If you haven't already you'll need to register to download the source, but registration is free.)

In the previous article, we got a class from a name (NSString*). But what if we want to list out all the classes loaded in the runtime? Fire up your editor of choice (I used ProjectBuilder and started with a "Tool" project) and make sure the header files from objc are in your include path (they should be by default).

#import <stdio.h>
#import <objc/objc-runtime.h>
#import <objc/hashtable.h>
void showIvars(Class klass) {  /* code is below */ }
void showMethodGroups(Class klass, char mType) { /* code is below */ }
int main (int argc, const char *argv[]) {
    NXHashTable * class_hash = objc_getClasses(); // NOT in a multi-threaded*** App.
    NXHashState state = NXInitHashState(class_hash);
    struct objc_class * klass;
    while (NXNextHashState(class_hash, &state, (void **)&klass)) {
        printf ("%s\n", klass->name);
        showIvars(klass);
        showMethodGroups(klass, '-'); // instance methods
        showMethodGroups(klass->isa, '+'); // class methods
    }
    return 0;
}

Please note -- The runtime code changed somewhat since Mac OS X. It now uses locks to support multi-threaded access. The code above will work on the greatest number of deployed systems. For details on how to use the newer function, see the comment for objc_getClassList() in objc-runtime.h or see the code in AllClasses.m of the RuntimeBrowser.

Compile this and run it. Since showIvars() and showMethodGroups() are just stubs -- which we'll fill in later -- you'll get class names without other details. Which classes you see depends on which Frameworks (or other ObjC binaries) you've linked in. By default, using ProjectBuilder, you'll see all of the classes in the Foundation.framework. This includes hidden, undocumented classes and sub-classes of class clusters like NSString!

Take a look at objc/objc.h. Among other things, you'll see:

typedef struct objc_class *Class;

Yup, a Class is a pointer to struct objc_class as advertised. Once we've got a Class we access runtime information from this struct. While we're here let's look for a moment at the definition of type id (the universal Objective-C object pointer) also defined in objc/objc.h:

typedef struct objc_object {
    Class isa;
} *id;

An object of type id is a pointer to a struct that contains a single element: an isa pointer to its struct objc_class in the runtime. That's it.

Objc_class is defined, surprisingly enough, in objc/objc-class.h. We're going to use it to extract info from the runtime. Let's look at its parts.

struct objc_class {
    struct objc_class *isa;	
    struct objc_class *super_class;	
    const char *name;		
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
};

The first and second elements are pointers to objc_class structs. The first is the isa pointer for the class. Class objects are full-fledged objects: they each have an isa pointer to their Class. The way it works is this: an object's isa points to the Class. That Class (struct objc_class) contains all the instance variables (objc_ivar_list) declared in the Class, but its objc_method_list contains only the instance methods defined with the class.

The Class pointed to by the Class' isa (the Class' Class) contains the Class' class methods in its objc_method_list. Got that? The terminology used in the runtime is that while an object's isa points to its Class, a Class's isa points to the object's "meta Class".

So what about a meta Class' isa pointer? Well, that points to the root class of the hierarchy (NSObject in most cases). (In the Foundation framework each meta class of a subclass of NSObject "isa" instance of NSObject.) And yes, by the way, NSObject's meta Class' isa points back to the same struct -- it's a circular reference so no Class' isa is ever NULL.

Whew! The relevant bit out of all that: the object's Class has the instance methods, the class's Class (a.k.a., meta Class) has the class methods.

Building Cocoa Applications: A Step by Step Guide

Related Reading

Building Cocoa Applications: A Step by Step Guide
By Simson Garfinkel, Michael Mahoney

The next part of the struct is the super_class, a pointer to a class' superclass: the class it inherits from. The super_class pointer is NULL for root classes in the hierarchy (e.g., NSObject).

The const char *name is, big surprise, the name of the class.

The long version records information about which version of the compiler a class was compiled with. It’s checked in the runtime to see if certain features are available for a given class. We can ignore this.

Comment on this articleThoughts, feedback, comments about the content of the article? Let us know.
Post your comments

Long info contains information about the class structure: whether it's a meta class, whether it's posing as another class, etc. Again this is of use mostly to the internal functioning of the runtime and we'll ignore it. The objc/objc-class.h file contains a list of #defined values (just after the struct objc_class definition) if you're interested.

The long instance_size is the number of bytes occupied by an instance of this class.

Each of a class' instance variables (ivars) is represented by a struct objc_ivar which contains the name (ivar_name), encoded type information (ivar_type) and the ivar's offset from the instance's address in memory. You can access the structs for all of a Class' ivars by traversing its struct objc_ivar_list *ivars.

OK, enough abstract background, let's use this. Let's take a look at some of this rich trough of information. Here's how to show the ivars:

void showIvars(Class klass) {
  int i;
  Ivar rtIvar;
  struct objc_ivar_list* ivarList = klass->ivars;
  if (ivarList!= NULL && (ivarList->ivar_count>0)) {
    printf ("  Instance Variabes:\n");
    for ( i = 0; i < ivarList->ivar_count; ++i ) {
        rtIvar = (ivarList->ivar_list + i);
        printf ("    name: '%s'  encodedType: '%s'  offset: %d\n",
                rtIvar->ivar_name, rtIvar->ivar_type, rtIvar->ivar_offset);
    }
  }
}

RuntimeBrowser

At a number of places in this article I refer to the "RuntimeBrowser" (or RB). It's an application like the JavaBrowser but for Objective-C. It makes use of the information shared in this article. The source code is released under the GNU Public License, so you can download it and run it yourself. In addition to being a useful development tool in its own right, the RB provides another way to learn more about the intricacies of the ObjC runtime, including how to decode the type encodings. Enjoy.

But be warned: you'll get a lot of information. Better to display this amount of detail for individual classes. Note this code displays type information as it is encoded in the runtime. The RuntimeBrowser (see sidebar) decodes this information back to the more familiar .h file form.

In a similar way you can get information about all the methods implemented by a class. Remember a class holds the instance methods; you have use the "meta class" (klass' isa) to get information about class methods. Here's a function to display method information:

void showMethodGroups(Class klass, char mType) {
  void *iterator = 0;     // Method list (category) iterator
  struct objc_method_list* mlist;
  Method currMethod;
  int  j;
  while ( mlist = class_nextMethodList( klass, &iterator ) ) {
    printf ("  Methods:\n");
    for ( j = 0; j < mlist->method_count; ++j ) {
        currMethod = (mlist->method_list + j);
        printf ("    method: '%c%s'  encodedReturnTypeAndArguments: '%s'\n", mType,
                (const char *)currMethod->method_name, currMethod->method_types);
    }
  }
}

Again, this produces a lot of information and is best used to view details of a single class.

The remaining elements of struct objc_class include:

Notice in showMethodGroups() there's a nested loop. What's going on here? Well the inner loop is of methods. And the outer loop is over groups of methods or ... categories. So now, with a little more knowledge of method lookup, we can answer the question from the first article: if you've got two methods with the same name defined on a class (at least one therefore in a category) which one gets invoked?

During method lookup, the runtime traverses the arrays of methods in the order they are stored. That order is the reverse of the order in which each group of methods is loaded. As the class must be loaded before categories are added to the class, the methods declared in a class itself are always loaded first, so end up last in the list. Since the runtime does method lookups from first-to-last, a category method will always override a method declared in the class itself.

If two different Categories define the same method, however, then it all depends on the order in which the bundles that contain those categories are loaded when an application is launched. To a large extent you can control this order by forcing bundles to be loaded when a program first starts, and being wary of loading bundles dynamically. But generally the rule is: don't redefine category methods in another category as this can lead to inconsistent results...

Please let me, or the good folks at O'Reilly, know what you think about this article. If you've noticed errors or thought of ways in which the presentation could be improved, feel free to comment in the TalkBack section at the bottom of this page.

Ezra Epstein is a long time Objective-C programmer who enjoys working on Mac OS X.


Return to MacDevCenter.com.

Copyright © 2009 O'Reilly Media, Inc.