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


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.)

If you want to know how many parameters a method expects, just count the colons in the selector. A selector (SEL) is a const char* (that has been uniqued) so you can use standard C string functions.

For a method with more than two parameters no methods are defined. Instead we need to invoke the underlying runtime function. It's easy. The function is objc_msgSend(), which is defined as:

    id objc_msgSend(id self, SEL op, ...);

So objc_msgSend() takes an object upon which a method should be invoked (a "target"), and a selector (or action) to perform, along with a variable number of arguments.

    #import <objc/objc-runtime.h>; // for objc_msgSend()
    SEL fiveArgumentSelector = @selector(aMethod:that:takes:five:arguments:);
    id result = objc_msgSend(myCustomInstace, fiveArgumentSelector,
		arg1, arg2, arg3, arg4, arg5);

An exception is raised if you "send" a message to an object which doesn't implement the corresponding method. Note we've #imported a new header. On Mac OS X you’ll find the ObjC headers in /usr/include/objc (on other systems, try: System/Developer/Headers/objc).

Related Reading

Learning Cocoa
By Apple Computer, Inc.

One of the many elegant and clever uses of dynamic binding is "delegates". Delegates are widely used in the AppKit, allowing you to effectively customize standard AppKit objects by intervening at decision points and thereby modifying behavior. Rather than registering a string of callbacks, you implement whichever delegate methods you want to catch (such as: -windowWillClose:).

Thanks to dynamic typing and dynamic binding, the implementation can be on ANY class. The cool thing here about the dynamism of ObjC is that you don't need to conform to a Protocol or otherwise implement all the delegate methods: each delegate method will be invoked only if it is implemented.

This works because we can query the runtime about which methods an object implements. Once again NSObject provides a convenient O-O interface to the runtime. Does an object implement a method? "Ask" the object:

	if ([aDelegate respondsTo:someSelector]) ...

Here's the declaration:

	- (BOOL)respondsToSelector:(SEL)aSelector;

Another feature of dynamic binding allows container objects (NSArray, NSDictionary, NSSet, etc.) to message their contained objects consistently, without requiring those objects to reside at a particular place in the inheritance hierarchy or adopt a formal Protocol. NSArray declares the following:

	- (void)makeObjectsPerformSelector:(SEL)aSelector;
	- (void)makeObjectsPerformSelector:(SEL)aSelector 
		withObject:(id)argument;

Which invokes -(id)performSelector:(SEL)aSelector and -(id)performSelector:(SEL)aSelector withObject:(id)object, respectively on each object contained by the NSArray. You can add another makeObjectsPerform... which takes 2 (or 3 or 4) arguments using a Category.

Categories

Categories are a feature of Objective-C that allows you to extend the functionality of ANY class. You don't need the source code of the class, no need to re-compile it either. Just declare a category, implement the method, and presto! All instances of the class or of any subclass now respond to your new methods. One usage of categories is to add convenience methods:

    @implementation NSString (NSString-Extensions)
	- (BOOL) containsString:(NSString *)aString;
	/*" Returns YES if the receiver contains aString, NO otherwise. "*/
	{ return ([self rangeOfString:aString].length != 0); }
    @end

But you can add any functionality you need. Sometimes it's the best way to get something done. At other times it's not and you may want to implement "helper" functions instead. For example:

    BOOL doesStringContainSubstring(NSString* string, NSString *substring)
    { return (string != nil && [string rangeOfString:substring].length != 0); }

With a Category you can add class or instance methods, but you can't add instance variables.

Another use of Categories is grouping functionality and coding responsibility in a group of developers. Putting category methods in separate source (.h and .m) files can be a useful way of organizing development of your own classes.

The MiscKit makes extensive use of categories and provides examples of good Category usage along with code you may want to use.

Overidding Behavior

Another thing categories let you do is override and customize behavior for existing methods. Although I've encountered times when this let me easily do things that would otherwise have been a lot of work, it is a feature that needs care. If you override a low-level method (a dispatch method on NSObject, for instance) this can have serious side effects. Such usage is generally NOT recommended. It's always a good idea to test your code thoroughly; overriding an object's methods in a category demands good testing.

Overriding a method in a category requires no special code, just declare and implement the method.

    @implementation NSObject (ObjectOverride)
    - description {
	return [NSString stringWithFormat:@"<objc-object>%@</objc-object>", [self class]];
    }
    @end

Note, as already mentioned, this is not recommended and usually leads to unwanted side effects. In cases like this, use categories to extend classes:

    @implementation NSObject (XMLDescription)
    - xmlDescription {
	return [NSString stringWithFormat:@"<objc-object>%@</objc-object>", [self class]];
    }
    @end

You're free to implement both instance and class methods in categories.

If you want more background on the Objective-C language and its features, an excellent resource is available on Apple's developer Web site at: http://developer.apple.com/techpubs/macosx/Cocoa/XObjC/XObjC.pdf

Final Thoughts

Now that you've begun your exploration of runtime, I hope you'll take a look at next week's article that digs deeper and looks at how it's implemented. Plus, you'll get a look at the runtime browser, which I think you'll find very useful. See you then!

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


O'Reilly & Associates published Building Cocoa Applications: A Step By Step Guide by Michael Mahoney & Simson Garfinkel in May, 2002.

You can also look at the Full Description of the book.

For more information, or to order the book, click here.


Return to the Mac DevCenter.

Copyright © 2009 O'Reilly Media, Inc.