NSObject 类与协议

Mike Ash Friday Q&A 中文译文:NSObject 类与协议

作者 TommyWu
封面圖片: NSObject 类与协议

译文 · 原文: Friday Q&A 2013-10-25: NSObject: the Class and the Protocol · 作者 Mike Ash

原文:https://www.mikeash.com/pyblog/friday-qa-2013-10-25-nsobject-the-class-and-the-protocol.html 发布:2013-10-25 作者:Mike Ash 译者:MiMo(mimo-v2.5-pro);代码块保留英文原样


读者 Tomas Bouda 提问:NSObject 协议是怎么回事?Cocoa 里有两个 NSObject,一个类和一个协议。为什么两者都要存在?它们各自有什么作用?今天的文章里,我将探讨这个问题的答案。

命名空间(Namespaces) 首先,我们来看这两个同名的实体是如何共存的。在 Objective-C 中,类和协议占据完全独立的命名空间。你可以拥有一个类和一个协议,它们在语言层面毫无关联,却具有相同的名字。NSObject 的情况正是如此。

从语言层面来看,没有任何地方会要求你必须使用类名或者协议名。类名可以用作消息发送(message sending)的目标、在@interface声明中使用,以及用作类型名。协议(protocol)也可以在一些相同的地方使用,但总是以不同的方式。因此,拥有一个同名的类和一个同名的协议并不会产生歧义。

根类(Root Classes) 类 NSObject 是一个根类(root class)。根类是指位于层次结构最顶端的类,这意味着它没有超类(superclass)。在 Objective-C 中,与 Java 等一些语言不同,可以存在多个根类。

Java 有一个单一的根类 java.lang.Object,所有其他类都直接或间接地继承自它。正因如此,Java 代码可以确信它遇到的任何对象都实现了 java.lang.Object 中的基本方法。

Cocoa 有多个根类。除了 NSObject,还有 NSProxy 以及其他一些零散的根类。这正是 NSObject 协议存在的原因之一。NSObject 协议定义了一组所有根类都预期会实现的基本方法。这样,代码就可以确信这些方法是存在的。

NSObject 类遵从 NSObject 协议,这意味着 NSObject 类实现了这些基本方法:

@interface NSObject <NSObject>

NSProxy 同样遵循 NSObject 协议(译注:NSProxy 是 Objective-C 中用于实现消息转发的抽象基类,与 NSObject 平级)。

@interface NSProxy <NSObject>

NSObject 协议包含诸如 hashisEqual:description 等方法。NSProxy 遵守 NSObject 协议这一事实意味着,你仍然可以依赖 NSProxy 的实例来实现这些基本的 NSObject 方法。

关于代理的补充说明

既然说到这里,那么为什么存在一个 NSProxy 根类呢?

在某些情况下,拥有一个没有实现太多方法的类是很有用的。正如其名,代理对象(proxy objects)就是一个常见的有用场景。NSObject 类实现了许多超出 NSObject 协议的内容,比如键值编码(key-value coding),而你可能并不需要这些。

在构建代理对象时,目标通常是让大部分方法保持未实现状态,以便可以通过类似 forwardInvocation: 的方法进行批量转发。继承自 NSObject 会引入很多不必要的 “包袱”,这些会带来干扰。NSProxy 通过为你提供一个没有那么多额外内容的更简单的父类,帮助你避免这个问题。

协议

NSObject 协议对根类有用的这一点,对于大多数 Objective-C 编程来说并不那么有趣,因为我们并不经常使用其他根类。然而,在自定义协议时,它就变得非常方便了。假设你有一个这样的协议:

@protocol MyProtocol
- (void)foo;
@end

现在你有一个指向符合该协议的对象的指针:

id<MyProtocol> obj;

你可以让这个对象执行 foo 操作:

[obj foo];

然而,你无法询问对象的描述信息:

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

而你无法检查其相等性:

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

一般而言,你无法要求它执行普通对象能做的任何操作。有时这无关紧要,但有时你确实希望具备这些能力。

这就是 NSObject 协议 的用武之地。协议可以继承其他协议。你可以让 MyProtocol 继承 NSObject 协议:

@protocol MyProtocol <NSObject>
- (void)foo;
@end

这表明,遵循 MyProtocol 的对象不仅能响应 -foo 消息,还能响应 NSObject protocol 中定义的所有那些通用消息。由于应用程序中的每个对象通常都继承自 NSObject 类,而该类遵循 NSObject protocol,这并不会给实现 MyProtocol 的人增加任何额外要求,同时又允许你在实例上使用这些常用方法。

结论
存在两个不同的 NSObject 是框架中的一个奇特角落,但当你深入了解时,就会明白其合理性。NSObject protocol(协议)允许多个根类都具备相同的基本方法,同时也使得声明一个协议变得容易,该协议包含了任何对象都应具备的基本功能。NSObject 类遵循了 NSObject protocol,将一切融合在一起。

今天的内容就到这里。周五问答一如既往由读者建议驱动,所以如果你有希望在未来文章中看到的话题,请发送给我们!


#Original (English)

Source: https://www.mikeash.com/pyblog/friday-qa-2013-10-25-nsobject-the-class-and-the-protocol.html

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.

NamespacesFirst, 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 ProxiesWhile 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.

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

ConclusionThe 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!