AddThis Social Bookmark Button

Print

Inside the Objective-C Runtime

by Ezra Epstein
05/24/2002

Editor's note -- In this first part of a two-part series for advanced programmers, Ezra Epstein shows how to access the basic features of Objective-C runtime. He also takes a look at categories on classes. In the second article that runs next week, he digs a little deeper and starts to look 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.

Introduction

Once upon a time dynamism in languages (especially OO languages) was a point of debate. Essentially dynamism won: Java added "reflection" and C++ added Run-Time Type Information. And the out-and-out dynamic language, Objective-C or ObjC for short, was used to build Mac OS X's User Interface frameworks, dubbed "Cocoa."

With dynamism you can encode features that are configured as the program is running. This comes in extremely handy in a number of places, including writing generalized database access code (like in EOF), writing generalized HTTP request/response or Web Services systems (like with WebObjects), or building dynamic, custom user interfaces (e.g., with InterfaceBuilder).

One reason -- yes, there are others -- that scripting languages like Perl became so popular for building dynamic Web sites is they are inherently dynamic. Being interpreted (rather than compiled) runtime is "compile-time". Of course there's a performance penalty (and the lack of type checking -- which some might count as a feature -- among other issues) and that's where compiled dynamic languages like Objective-C come in.

Most compilers are designed with a single goal: translate the logic encoded in a format designed to be used by people (programming language) toward a format that tells a computer what to do (machine language). A small amount of "additional" information is retained to allow a linker to stitch together pieces of executable (machine) code (or to allow a programmer to debug the code).

This means that most compilers lose a lot of information. The Objective-C compiler is "smart". By that I mean that it doesn't "forget" all the intelligence you put into designing your code. Information about the structure of your code (things like class names and method names) is extracted by the compiler and stored in data structures (C structs). Those data structures are available when the program is executing and, together with functions for accessing and updating that information, comprise the Objective-C runtime.

Basic Features of Objective-C Runtime

Objective-C provides robust dynamism so, among other things, you don't have to "roll your own." Access to the ObjC runtime via ObjC (Cocoa) classes and functions comes with the Foundation framework. NSObjCRuntime.h defines several such functions and all classes that implement the NSObject Protocol (e.g., all subclasses of NSObject) have more built-in. The Foundation framework is on every Mac OS X machine. If you want to write code, however, you'll need to install the Developer package, which will add the header files for this and other frameworks.

Objective-C is, of course, object-oriented, so let's start with an object, or rather a class (of which the object is an instance). Since we're looking at dynamism, let's imagine we've got a text or XML-based config file (perhaps a property-list, a .plist). This config file will contain instructions, starting with the name of a class to use. With runtime access we can transform the name of a class (a string) into the Class itself:

    #import <Foundation/NSObjCRuntime.h>
    NSString *aClassName; // read from a "config" file
    Class namedClass = NSClassFromString(aClassName); 

If the named class doesn't exist in the runtime (e.g., hasn't been loaded), NSClassFromString() returns Nil. (Note: We've imported the precise .h file above for clarity. Usually you #import <Foundation/Foundation.h> to take advantage of the speed of pre-compiled headers.)

Once we've got a class we'll want to invoke its methods. To do so we use a "selector" of type SEL.

    NSString *aMethodName;  // Exists. E.g., from a "config" file
    SEL aSelector = NSSelectorFromString(aMethodName);

Related Reading

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

If aMethodName does not refer to an actual method name on some class currently loaded in the runtime then aSelector will be NULL.

(Deeper: Selectors are used for method invocation. ObjC distinguishes method invocation from method implementation. It's a lot like having function pointers in C with the added *pow* that all functions are named and can be referenced by name. Functions with the same name can resolve to (different) implementations depending on an object's class. It sometimes takes a little while to get it, but when you do, you realize this amounts to a lot of power for the developer.)

Internally, a selector (SEL) is a const char*. See for yourself:

    printf("%s\n", aSelector);

(You may need to cast aSelector to const char* to avoid a warning from your compiler). SELs have one additional feature: they are unique within the runtime so two SELs of the same method exhibit pointer equality.

The NSObject Protocol defines a method that lets us invoke a selector on a class or an instance. On a class we just:

    SEL desc = NSSelectorFromString(@"description");
    [namedClass performSelector:desc];

In the invocation above, the runtime is being used to "dynamically bind" the selector to an underlying method implementation. In the case of ObjC, methods are always bound dynamically. Even when we invoke methods the "regular" way the invocations are dynamically bound to their corresponding implementations. (See Dynamic Binding in the Cocoa documentation for more details on this subject.)

Comment on this article Post your questions and comments about Objective-C runtime.
Post your comments

Let's get an instance of our named class:

    id anInstance = [[[namedClass alloc] init] autorelease];

(This assumes that namedClass's designated initializer is -init. See Allocation and Initialization: The Designated Initializer for more details.) Now we can message our instance of namedClass (if it inherits from NSObject or otherwise implements the NSObject Protocol):

    [namedClass performSelector:desc];

Things are pretty much the same when messaging (or invoking) a method that takes parameters. NSObject defines two convenience methods:

	- (id)performSelector:(SEL)aSelector withObject:(id)object;
	- (id)performSelector:(SEL)aSelector withObject:(id)object1 
		withObject:(id)object2;

The methods specified by aSelector should return an object. (If your method returns a C type, you can wrap the result in an NSValue before returning. Otherwise, to message methods that do not return an object, you'll need to use NSInvocation.)

Pages: 1, 2

Next Pagearrow