Macs, Modularity and More

Reflections on Objective-C, Part II

In my last Reflections on Objective-C post, I put forward some of my thoughts about Objective-C's approach to memory management, specifically, the NSAutoreleasePools. This drew some flak from developers pointing out that reference counting is neither unique to Objective-C, nor the only way of handling memory management, both points which I agree with. However, my point was this is ubiquitous in Objective-C applications, which is not necessarily the case with other approaches cited in the comments. One commenter tried to brush the issue under the rug by claiming that NSAutoreleasePools are part of Apple's CoreFoundation rather than the Objective-C syntax. (Well, that's true; but technically, 'malloc' is not part of the C language syntax either; but it's present in all C libraries.) But let's put that behind us ...

In this post, I want to talk about Objective-C's approach to dealing with messaging objects, particularly with reference to the dynamic aspects.

Although Objective-C is a super-set of the C programming language, and is thus typesafe and compiled, in actual fact the dynamically of the language goes past traditional compiled languages to behave more like Smalltalk, upon which it is based. To understand how this works, it's worth reviewing how messages in Objective-C work.

Objective-C classes are split into interface and implementation sections. The interface exports the data layout for the class (under the covers – in the old ABI, at least – is simply a structure which contains an ordered set of strongly typed variables or ivars), as well as the public set of methods which are known to the class. The implementation contains implementations of these methods, but may also contain additional messages not present in the interface (for example, to implement private or internal functionality).

So far, this hasn't differed from how other languages may work (Java combines both the interface and implementation structures in one unit; an interface in Java corresponds to a Protocol in Objective-C). You can send a message (referred to as a 'method call' in other languages) to any object, and the compiler verifies that the message sent is known to the interface of the type of the variable. As an example, you can send the “length” message to an NSString, and get back the number of characters contained.

There's two key differences between other compiled OO languages and Objective-C, however. Firstly, it's possible to send a message to an object which it doesn't know about. That is, you can programmatically send “longth” to an NSString. If you define the target to be of type NSString, you'll get a compiler warning (though warnings are promotable to errors) saying that it “may not respond to 'longth'”.

Why would a feature which allows errors to creep in be considered a benefit? Well, as well as being referred to as as specific type (or one of its superclassses), there is a generic type called id. If you were to send “longth” to NSObject, you'd get a warning (much like you would with an NSString target). However, you can send anything to a target of type id without any compiler errors or warnings.

In either case, and whether the receiver's interface declares whether the message is known or not, the instance may have an implementation for the method. If the method is implemented, it will be invoked. This means you can send “count” to id objects, and if they respond to this message you'll get a result back. (In dynamic languages, this is referred to as 'duck typing'.)

Being able to send messages to objects which may not understand them might seem a little strange. However, this happens all the time in Java where “Method.invoke()” is used to invoke methods which may not have been known about at compile time.

This is used to great effect to implement a couple of patterns in Objective-C. The first is delegates, where standard UI components call back application code in response to specific events.

The standard UI framework classes obviously know nothing specific about the classes implemented by end application developers. However, they do call back with certain messages (like touchesBegan or windowShouldClose). In these cases, the delegate is typed as id, so any arbitrary messages can be sent. This is also one of the key reasons that the Objective-C frameworks are so extensible; instead of requiring arbitrary extensions to be defined and implemented, the framework can send a message whether it is implemented or not. As new features – and therefore methods – are added, client classes don't need to be recompiled.

The second pattern this allows is dynamic proxying. Java has a java.lang.reflect.Proxy which can implement a Java interface and forward all methods through a single handler. However, its use is often clumsy and if multiple methods are required then the invocation handler has to provide the switching logic itself.

In Objective-C's case, the base NSObject has a method called forwardInvocation:, which acts as a default method if the instance does not respond to the message sent.

This is used to implement Remote Messaging. This allows a thin proxy to intercept messages on a local machine, forward them over a network protocol, and then invoke the object on the remote side transparently. Unlike (say) RMI or CORBA, you don't need to generate any compile-time stubs or implementation ahead of time; it can be dynamically added to a class at runtime.

As with my previous post, this isn't meant to say that responding to dynamic messages or invoking messages can't be done in other languages; rather, the fact that it's built into the base NSObject runtime means that every object can take advantage of this runtime functionality. And even if it seems that there isn't any benefit from doing this, there's a lot of code which uses this functionality to good effect in the iOS/OSX frameworks.