mikeash.com: just this guy, you know?

Posted at 2013-10-25 13:27 | RSS feed (Full text feed) | Blog Index
Next article: VoodooPad Acquisition
Previous article: Friday Q&A 2013-10-11: Why Registers Are Fast and RAM Is Slow
Tags: fridayqna objectivec
Friday Q&A 2013-10-25: NSObject: the Class and the Protocol
by Mike Ash  
This article is also available in Chinese (translation by neoman).

Reader Tomas Bouda asks: what's the deal with the NSObject protocol? There are two NSObjects in Cocoa, a class and a protocol. Why both? What purpose do they serve? In today's article, I'll explore the answer to this question.

Namespaces
First, let's look at how these two entities with the same name can coexist. Classes and protocols in Objective-C inhabit entirely separate namespaces. You can have a class and a protocol, which are unrelated at the language level, with the same name. That's the case with NSObject.

If you look at the language, there are no places where you can use either a class name or a protocol name. Class names can be used as the target of message sends, in @interface declarations, and as type names. Protocols can be used in some of the same places, but always in a different way. There's no ambiguity in having one of each with the same name.

Root Classes
NSObject the class is a root class. A root class is a class at the very top of the hierarchy, meaning that it has no superclass. In Objective-C, unlike some languages like Java, there can be more than one root class.

Java has a single root class, java.lang.Object, which every other class directly or indirectly inherits from. Because of this, Java code can count on any object it encounters implementing the basic methods in java.lang.Object.

Cocoa has multiple root classes. In addition to NSObject there's also NSProxy and some other assorted root classes. This is part of the reason for the NSObject protocol. The NSObject protocol defines a set of basic methods that all root classes are expected to implement. This way, code can count on those methods being there.

The NSObject class conforms to the NSObject protocol, which means that the NSObject class implements these basic methods:

    @interface NSObject <NSObject>

NSProxy also conforms to the NSObject protocol:

    @interface NSProxy <NSObject>

The NSObject protocol contains methods like hash, isEqual:, description, etc. The fact that NSProxy conforms to NSObject means that you can still count on instances of NSProxy implementing these basic NSObject methods.

An Aside About Proxies
While we're at it, just why is there an NSProxy root class?

There are some cases where it's useful to have a class that doesn't implement very many methods. As the name suggests, proxy objects are a common case where this is useful. The NSObject class implements a lot of stuff beyond the NSObject protocol, such as key-value coding, that you don't necessarily want.

When building a proxy object, the goal is generally to leave most methods unimplemented so that they can be forwarded in bulk using a method like forwardInvocation:. Subclassing NSObject would pull in a lot of baggage that would interfere. NSProxy helps to avoid this by giving you a simpler superclass that doesn't have so much extra stuff in it.

Protocols
The fact that the NSObject protocol is useful for root classes isn't all that interesting for most Objective-C programming, since we don't use other root classes very often. However, it becomes really handy when making your own protocols. Say you have a protocol like this:

    @protocol MyProtocol

    - (void)foo;

    @end

And now you have a pointer to an object that conforms to it:

    id<MyProtocol> obj;

You can tell this object to foo:

    [obj foo];

However, you cannot ask the object for its description:

    [obj description]; // no such method in the protocol

And you can't check it for equality:

    [obj isEqual: obj2]; // no such method in the protocol

In general you can't ask it to do any of the stuff that a normal object can do. Sometimes this doesn't matter, but sometimes you actually want to be able to do this stuff.

This is where the NSObject protocol Comes in. Protocols can inherit from other protocols. You can make MyProtocol inherit from the NSObject protocol:

    @protocol MyProtocol <NSObject>

    - (void)foo;

    @end

This says that not only do objects that conform to MyProtocol respond to -foo, but they also respond to all those common messages in the NSObject protocol. Since every object in your app typically inherits from the NSObject class and it conforms to the NSObject protocol, this doesn't impose any additional requirements on people implementing MyProtocol, while allowing you use these common methods on instances.

Conclusion
The fact that there are two different NSObjects is an odd corner of the frameworks, but it makes sense when you get down to it. An NSObject protocol allows multiple root classes to all have the same basic methods, and also makes it easy to declare a protocol which also includes basic functionality expected of any object. The NSObject class conforms to the NSObject protocol, bringing everything together.

That's it for today. Friday Q&A is driven by reader suggestions as always, so if you have a topic you'd like to see covered in a future edition, please send it in!

Did you enjoy this article? I'm selling a whole book full of them. It's available for iBooks and Kindle, plus a direct download in PDF and ePub format. It's also available in paper for the old-fashioned. Click here for more information.

Comments:

BergQuester at 2013-10-25 15:52:05:
The mechanics of this make sense. However, why have a protocol to enforce this rather than taking all of the methods in the NSObject protocol and creating the One True Root Class that NSObject, NSProxy, et. al. can inherit from?

Is the choice between a new root class and a protocol just a historical detail?

Bleyddyn at 2013-10-25 16:26:25:
I always inherit from the NSObject protocol because I've yet to run across a case where leaving it out was necessary and I almost always end up wanting to quiet compiler warnings if I don't.

Anyone have any examples where it might be a good idea to leave it off?

KenThomases at 2013-10-27 05:42:09:
BergQuester, root classes are special in that class objects also respond to the instance methods of the root class. Since we want the class objects of subclasses of NSObject to respond to much of the same goodness of NSObject, we'd have to lift that goodness into the root class if NSObject were not the root class. Then that goodness would also be in NSProxy and defeat the purpose of making NSProxy minimal.

Also, you'd still want the NSObject protocol for the purpose of inheriting into other protocols, as Mike explained. So you wouldn't gain anything by having a One True Root Class.

BergQuester at 2013-10-28 20:44:51:
KenThomases, inheriting the superclass's (in our case a root class) instance methods is a major reason to subclass. Every instance method declared in the NSObject protocol, except debugDescription, is required. Whether or not these methods were moved to a OneTrueRootClass, NSProxy is required to respond to these methods.

Maybe there's more overhead in consolidating these shared methods than I am aware of, or perhaps the implementation details are so radically different for each of these methods that the code is entirely different. On the surface though, things like retain, release, autorelease, self, perform selector, etc would seem to have a near universal implementation between NSObject and NSProxy.

BergQuester at 2013-10-29 04:28:31:
Looking at the GNUStep implementation of NSObject and NSProxy (I was able to find the source of Apple's NSObject on their open source page, but not NSProxy), over half of the methods are exact or near exact duplicates with minor variations (preprocessor macro differences, variable names). The rest either isProxy forward the method onto the 'real' object (e.g. performSelector:, isKindOfClass:, respondsToSelector:, etc).

Granted, this is GNUStep and not Apple's code and it's not that much code. But assuming Apple shares this amount of duplication between the two, it seems odd to me not have a OneTrueRootClass.

The reason NSProxy doesn't subclass the NSObject class is because, as Mike points out, there's a lot of extra baggage that comes with the class itself. But I'm still failing to see any significant impact in moving just the NSObject protocol methods to a OneTrueRootClass, aside from historical considerations.

Ari Weinstein at 2013-10-29 17:35:47:
This is a silly question, but I must ask:

Wouldn't this have the same effect, assuming NSObject is the root class?

NSObject<MyProtocol> *obj;

In fact, due to the protocol and class namespaces being different, you could even do:

typedef NSObject<MyProtocol> MyProtocol;
MyProtocol *obj;

Obviously this is a bad idea for readability and probably other reasons, but it does work - you can even send messages the "MyProtocol" class, though obviously they will end up being sent to NSObject itself.


I've actually used this method when implementing a UIViewController class-cluster type thing, where I couldn't cleanly implement a common superclass because some of the classes had to be subclasses of other UIViewControllers. For example, one is a subclass of QLPreviewController and yet another is a subclass of PKAddPassesViewController.

So I implemented the class cluster as a category on UIViewController itself, created a protocol for the "subclasses" implement, and then typedef'd UIViewController<DCPushPreviewController> to DCPushPreviewController, and because UIViewController itself adopts DCPushPreviewController, everything works out perfectly, including sending messages to the "DCPushPreviewController" class.

I'd be thrilled to hear if there was a cleaner way to do this.

Mark Aufflick at 2013-11-04 20:04:38:
Just thought I'd point out that MAProxy doesn't adopt the NSObject protocol ;)

https://github.com/mikeash/MAFuture/blob/master/MAProxy.h

Mark Aufflick at 2013-11-04 20:09:37:
@Arl one answer is that it's a question of declaring intent. Your class doesn't care if someone passes in an object that has any particular inheritance structure, all you care about is whether it can respond to a particular set of messages.

Crispin Bennett at 2013-11-21 20:21:32:
Why do some of Apple's protocols (eg. NSFastEnumeration) *not* inherit from <NSObject>?

Two possibilities I've seen canvassed are (1) that Apple anticipates some protocols may be used with classes not themselves using NSObject as a root class, or (2) to differentiate (semantically) between protocols specifying whole application roles (eg. delegate protocols) and those specifying only individual behaviours (eg. being enumerable).

Gwynne Raskind at 2013-11-21 20:49:42:
@Crispin Bennett: Neither; it's because most of those protocols were added to the headers before the convention of adding <NSObject> to protocols came around. In short, it's backwards compatibility; changing it now would break source compatibility and possibly binary compatibility as well.

Crispin Bennett at 2013-11-21 20:53:00:
@Gwynne Aha. So nothing really to puzzle over there.


Comments RSS feed for this page

Add your thoughts, post a comment:

Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.

Name:
Web site:
Comment:
Formatting: <i> <b> <blockquote> <code>. URLs are automatically hyperlinked.
Code syntax highlighting thanks to Pygments.
Hosted at DigitalOcean.