Home > Articles > Home & Office Computing > Mac OS X

  • Print
  • + Share This
From the author of Fast Proxies

Fast Proxies

When you send a message on Objective-C, you call a runtime library function that finds the function used to implement the corresponding method. With the NeXT runtime, this is objc_msgSend(), which both looks up and calls the method function. With the GNU runtime, you call objc_msg_lookup() and then call the returned function yourself.

Typically, this lookup follows a fast path, either finding it in a cache or in a sparse array structure (or linked list on the NeXT runtime), mapping selectors to methods for a given class. When this fails, traditionally a forwarding function is called.

The forwarding function is horrible code which, fortunately, most developers never need to go near. It uses the method signature to find what the arguments for the method are and then the C variadic function macros to retrieve each of them in turn and add them to an NSInvocation object. This is then passed to -forwardInvocation:, and Objective-C programmers can pretend that they are using a Smalltalk-like language with stack introspection and that none of this is ugly code exists.

As you can imagine, this is very slow. For every argument, the forwarding function needs to look up the type and then copy the argument into the NSInvocation object.

OS X 10.5 introduced a couple of methods for making this a bit faster. These are called by the lookup function just before it calls the forwarding function, and gives the class an opportunity to install new methods in the class's method list. These are nice if you want to lazily create methods, but don't help much with proxies.

The most common use for proxies is to simply forward messages from one object to another. The method is not modified, it is just passed from one object to another.

10.6 added a new hook to the runtime that makes this trivial. The -forwardingProxyForSelector: method is now called before jumping to the forwarding function. This allows the object being sent a message to alter the message receiver. The return value for this method is an object, and the runtime will redirect the message to this receiver.

The content accessing proxies that I mentioned earlier are an obvious use-case for this. They send a message to the object that they wrap when they are created, and another one when they are destroyed, but simply forward every message that they are sent to the object that they wrap.

The implementation of this proxy is trivial. The forwarding is implemented by a method like this:

- (id)forwardingProxyForSelector: (SEL)aSelector
{
    return obj;
}

This is, of course, slightly slower than a direct message send, but the difference is not nearly as pronounced as if you go via the forwarding mechanism.

The nice thing about this method is that it doesn't add anything in terms of expressiveness. You can implement exactly the same forwarding behavior using -methodSignatureForSelector: and -forwardInvocation:; it will just be (a lot) slower. This means that you can add this method to your proxy classes without worrying about backwards compatibility. As long as you also implement the more traditional forwarding mechanism, your code will work on older versions of OS X (and GNUstep), but when it runs on 10.6 it will be a lot faster.

Proxy objects are not the only things that benefit from this. Pretty much any OS X GUI includes some subclasses of NSControl. Every button, for instance, is an NSButton instance, which is a subclass of NSControl.

The NSControl class is a simple NSView subclass that delegates all of its drawing and event handling to an NSCell. If you read the documentation of NSButton and compare it to NSButtonCell, you will notice a striking similarity. Almost all the methods declared on NSButton are really stubs that just forward to the cell. These are things like:

- (BOOL)isEnabled
{
    return [button isEnabled];
}

There are several hundred methods like this in the various NSControl subclasses. You can replace all of them with a single method in NSControl:

- (id)forwardingProxyForSelector: (SEL)aSelector
{
    return _cell;
}

This simplifies development, and may even make things faster. These methods are not speed-critical, so the small overhead from going via the forwarding mechanism is not very large. However, the extra instruction cache you free up for the rest of your code to use by replacing a few hundred methods with one can give a nice speed boost to the bits of your code that are.

NSControl isn't the only place where this is important. Cocoa inherits from OpenStep a number of patterns that were later codified by the Gang of Four. Cocoa programmers, therefore, tend to favor composition and delegation over inheritance.

With the new forwarding mechanism, you can now trivially implement everything that inheritance provides without actually using inheritance. This includes multiple inheritance.

Inheritance, in theoretical terms, is a special case of delegation. If an object doesn't implement a given method, then inheritance means that it implicitly delegates its implementation to its superclass. With a language like C++ or Lisp with CLOS, it can delegate the implementation to one of several superclasses.

If you look at how C++ implements multiple inheritance, you see that each object is just a structure containing all of the fields declared by each of its superclasses. When you cast a pointer in C++, you are really doing some quite complex pointer arithmetic. You can implement something equivalent in Objective-C now by creating instance variables for each of the “superclasses” that your class has, and attempting to delegate to each in turn using the forwarding mechanism, like this:

- (id)forwardingProxyForSelector: (SEL)aSelector
{
    if ([super1 respondsToSelector: aSelector])
    {
        return super1;
    }	
    if ([super2 respondsToSelector: aSelector])
    {
        return super2;
    }	
    return super3;
}

The lookup will first see if this class implements the method, then look at each of the superclasses. If none of these implement a method for the selector, then it will call this forwarding function. This will first check if super1 implements the method and, if so, forward it the method. If not, then it will try super2 and finally super3. If this doesn't implement the method, then the older forwarding methods will be called.

You can speed this up a bit by using the runtime functions instead of -respondsToSelector:. Because composition is now very easy to implement and almost as fast as inheritance, it becomes a better choice in a lot of cases.

You can also use this to speed up some less obvious things. The first place I used this new forwarding mechanism was to accelerate Quentin Math[ae]e's -ifResponds higher-order message proxy. This adds a category on NSObject that returns a proxy when you send it an -ifResponds message. The proxy then forwards message that the receiver responds to and silently ignores other. You can use this when sending delegate messages, like this:

[[delegate ifResponds] doSomething];

The proxy returned by this method now uses the new forwarding mechanism when the receiver does respond to the specified selector, so using this path is now quite fast, and is a lot cleaner to write than putting lots of conditionals in the code.

  • + Share This
  • 🔖 Save To Your Account