Next article: VoodooPad Acquisition
Previous article: Friday Q&A 2013-10-11: Why Registers Are Fast and RAM Is Slow
Tags: fridayqna objectivec
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 ClassesNSObject
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!
Comments:
Anyone have any examples where it might be a good idea to leave it off?
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.
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.
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.
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.
https://github.com/mikeash/MAFuture/blob/master/MAProxy.h
<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).
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.
Is the choice between a new root class and a protocol just a historical detail?