译文 · 原文: Friday Q&A 2013-01-25: Let's Build NSObject · 作者 Mike Ash
原文:https://www.mikeash.com/pyblog/friday-qa-2013-01-25-lets-build-nsobject.html 发布:2013-01-25 作者:Mike Ash 译者:MiMo(mimo-v2.5-pro);代码块保留英文原样
NSObject 类是我们构建和使用的几乎所有类的根类,作为 Cocoa 编程的一部分。然而,它究竟做了什么,又是如何实现的呢?今天,我将根据博客朋友兼特约作者 Gwynne Raskind 的建议,从零开始重建 NSObject。
根类的组成部分
一个根类究竟需要做什么?就 Objective-C 语言本身而言,恰有一个要求:根类的第一个实例变量必须是 isa,它是一个指向对象所属类的指针。isa 在分发消息时用于确定对象属于哪个类。从严格的语言角度来看,仅此而已。
当然,一个只提供这点功能的根类不会很有用。NSObject 提供了更多功能。它提供的功能可分为三类:
-
内存管理:像
retain和release这样的标准内存管理方法在 NSObject 中实现。alloc方法也在其中实现。 -
内省:NSObject 提供了一系列方法,这些方法本质上是对 Objective-C 运行时(runtime)功能的封装,例如
class、respondsToSelector:和isKindOfClass:。 -
杂项方法的默认实现:每个对象都需要实现一系列方法,例如
isEqual:和description。为了确保每个对象都有实现,NSObject提供了默认实现,如果子类没有自行提供,就会继承这些默认实现。
代码实现
我将通过 MAObject 重新实现 NSObject 的功能。本文的完整代码已发布在 GitHub:
https://github.com/mikeash/MAObject
请注意,这段代码是在未启用 ARC 的情况下编译的。虽然 ARC 很优秀且应尽可能使用,但在实现根类(root class)时它确实会带来阻碍,因为根类需要自行实现内存管理,而 ARC 更倾向于将内存管理交由编译器处理。
实例变量
MAObject 有两个实例变量(instance variables)。第一个是 isa 指针(isa pointer)。第二个是对象的引用计数(reference count):
@implementation MAObject { Class isa; volatile int32_t retainCount; }引用计数的管理将使用来自 OSAtomic.h 的函数来确保线程安全,这就是为什么它采用了某种程度上非传统的定义,而不是直接使用 NSUInteger 或类似类型。
实际上,NSObject 在外部存储引用计数。存在一个全局表(global table),它将对象的地址映射到其引用计数值。这节省了内存,因为当引用计数为常见的 1 时,该表中根本没有对应的条目。然而,这种技术比较复杂且有点慢,因此在我自己的实现版本中,我选择不遵循它。
内存管理
MAObject 需要能够做的第一件事是创建实例。这是通过实现 +alloc 方法来完成的。(我跳过了已弃用且很少使用的 +allocWithZone:,这些天它做着同样的事情并忽略其参数。)(译注:在现代 Objective-C Runtime 和系统中,allocWithZone: 已基本不再被单独使用,alloc 是标准接口。)
子类很少重写 +alloc,并依赖根类为它们分配内存。这意味着 MAObject 不仅要能为自身分配实例,还要能为任何子类分配实例。实现这一点的方式是利用一个事实:在类方法中,self 的值是消息实际发送的目标类。如果代码执行 [SomeSubclass alloc],那么 self 就持有指向 SomeSubclass 的指针。随后,该类可以被用来查询运行时(runtime),以确定应分配多少内存,并正确设置 isa 指针。引用计数(retain count)也被初始化为 1,这正符合新分配对象的语义:
+ (id)alloc { MAObject *obj = calloc(1, class_getInstanceSize(self)); obj->isa = self; obj->retainCount = 1; return obj; }retain 方法(引用计数方法)仅使用 OSAtomicIncrement32 将引用计数(retain count)加一,并返回 self:
- (id)retain { OSAtomicIncrement32(&retainCount); return self; }release 方法做的事情要更多一些。它首先减少(decrement)引用计数(retain count)。如果引用计数减至 0,则该对象需要被销毁,因此代码会调用 dealloc:
- (oneway void)release { uint32_t newCount = OSAtomicDecrement32(&retainCount); if(newCount == 0) [self dealloc]; }autorelease 的实现会调用 NSAutoreleasePool 将自身添加到当前自动释放池(automatic release pool)。如今自动释放池已成为运行时的一部分,因此这算是某种间接路径;但运行时中的自动释放 API 属于私有接口,所以我们目前只能采用这种方案:
- (id)autorelease { [NSAutoreleasePool addObject: self]; return self; }引用计数方法(retainCount method)简单地返回实例变量(ivar)中存储的值:
- (NSUInteger)retainCount { return retainCount; }最后还有 dealloc 方法。在普通类中,dealloc 需要清理所有实例变量(instance variables)然后调用 super。而根类(root class)必须实际释放对象自身占用的内存。在此情况下,只需简单调用 free 即可:
- (void)dealloc { free(self); }此外还有一些辅助方法。NSObject 提供了一个空实现的 init 方法以保持一致性,这样子类总是可以调用 [super init]:
- (id)init { return self; }还有一个新方法,它只是对 alloc 和 init 的一个包装:
+ (id)new { return [[self alloc] init]; }这里还有一个空的 finalize 方法。NSObject 将其作为垃圾回收(garbage collection)支持的一部分来实现。MAObject 本身并不支持垃圾回收,但我加入这个方法仅仅是因为 NSObject 有这个方法:
- (void)finalize { }内省许多内省方法只是对运行时函数的封装。由于这个主题本身不够有趣,我会在讲解时也简要介绍运行时函数在底层是如何工作的。
最简单的内省方法是 class,它直接返回 isa 的值:
- (Class)class { return isa; }从技术上讲,这个方法在遇到标签指针(tagged pointers)时会失效。一个正确的实现应该调用 object_getClass 函数,该函数能正确处理标签指针,并从普通指针中提取 isa 指针。
superclass 实例方法实际上等同于在对象所属的类上调用 superclass 类方法,因此该方法的实际做法正是如此:
- (Class)superclass { return [[self class] superclass]; }这些操作同样有对应的类方法。+class 方法只是返回 self,也就是类对象本身。这有点奇怪,但 NSObject 就是这样实现的。[obj class] 返回对象的类,而 [MyClass class] 只是返回指向 MyClass 本身的指针。这并不一致,因为 MyClass 本身也有一个类,这个类就是 MyClass 的元类,但 Objective-C 的常规做法就是这样的:
+ (Class)class { return self; }+superclass 方法的功能正如其名。它是通过调用 class_getSuperclass 来实现的,该函数会在运行时维护的类结构中搜寻,并取出指向超类的指针。
+ (Class)superclass { return class_getSuperclass(self); }还有一些方法用于查询对象的类是否匹配特定类。简单的一种是 isMemberOfClass:(是否属于特定类),它进行严格检查,忽略子类。其方法实现很简单:
- (BOOL)isMemberOfClass: (Class)aClass { return isa == aClass; }isKindOfClass: 方法也会检查子类,因此 [subclassInstance isKindOfClass: [Superclass class]] 会返回 YES。此方法的输出本质上与类方法 isSubclassOfClass: 的输出相同,因此它只是直接调用后者:
- (BOOL)isKindOfClass: (Class)aClass { return [isa isSubclassOfClass: aClass]; }这个方法变得更有趣一些了。它从 self 开始,沿着类层次结构(class hierarchy)向上遍历,在每一层与目标类(target class)进行比较。如果找到匹配项,就返回 YES。如果到达类层次结构的最顶端仍未找到匹配项,则返回 NO:
+ (BOOL)isSubclassOfClass: (Class)aClass { for(Class candidate = self; candidate != nil; candidate = [candidate superclass]) if (candidate == aClass) return YES;
return NO; }值得注意的是,这个检查并非特别高效。如果在一个类层次结构(class hierarchy)很深的类上调用此方法,在返回 NO 之前可能需要经历多次循环迭代。正因如此,isKindOfClass: 检查可能比消息发送(message sending)慢得多,在某些情况下甚至会成为显著的性能瓶颈。这又多了一个尽可能避免使用它们的理由。
respondsToSelector: 方法会直接调用运行时函数 class_respondsToSelector。该函数则会在类的方法表(method table)中查找这个 selector(选择子),以确认其是否拥有对应的条目:
- (BOOL)respondsToSelector: (SEL)aSelector { return class_respondsToSelector(isa, aSelector); }有一个类方法 instancesRespondToSelector:,它与前者几乎完全相同。唯一的区别在于传递的是 self—— 在此上下文中即类本身 —— 而非 isa(此处应为元类)。
+ (BOOL)instancesRespondToSelector: (SEL)aSelector { return class_respondsToSelector(self, aSelector); }此外还有两个 conformsToProtocol: 方法,一个用于实例,一个用于类。它们也只是包装了一个运行时函数,该函数只是查询一个记录了该类所遵循的所有协议的表,以检查给定的协议是否存在:
- (BOOL)conformsToProtocol: (Protocol *)aProtocol { return class_conformsToProtocol(isa, aProtocol); }
+ (BOOL)conformsToProtocol: (Protocol *)protocol { return class_conformsToProtocol(self, protocol); }接下来是 methodForSelector: 及其更优雅的姊妹方法 instanceMethodForSelector:。这两个方法都通过调用 class_getMethodImplementation 来实现,该函数会在类的方法表(method table)中查找给定的选择子(selector),并返回对应的方法实现指针(IMP):
- (IMP)methodForSelector: (SEL)aSelector { return class_getMethodImplementation(isa, aSelector); }
+ (IMP)instanceMethodForSelector: (SEL)aSelector { return class_getMethodImplementation(self, aSelector); }这些方法的一个有趣特性是,class_getMethodImplementation 始终会返回一个 IMP(方法实现指针),即使对于未知的选择子(selector)也是如此。当类并未实际实现某个方法时,它会返回一个特殊的转发 IMP,该 IMP 封装了消息参数并开始执行调用 forwardInvocation: 的流程。
methodSignatureForSelector: 方法只是封装了等效的类方法:
- (NSMethodSignature *)methodSignatureForSelector: (SEL)aSelector { return [isa instanceMethodSignatureForSelector: aSelector]; }这个类方法依次包装了一些运行时调用。它首先为给定的选择子(selector)获取 Method(方法)结构体。如果找不到,就说明该类没有实现该方法,这段代码返回 nil。否则,它提取表示该方法类型的 C 字符串,并将其包装在一个 NSMethodSignature 对象中:
+ (NSMethodSignature *)instanceMethodSignatureForSelector: (SEL)aSelector { Method method = class_getInstanceMethod(self, aSelector); if(!method) return nil;
const char *types = method_getTypeEncoding(method); return [NSMethodSignature signatureWithObjCTypes: types]; }最后是 performSelector: 及其两个带有参数的 withObject: 变体方法。这些严格来说不属于自省(introspection)范畴,但它们同样属于封装底层运行时功能的通用类别。它们只是获取给定 selector(选择子)对应的 IMP(实现指针),将其转换为适当的函数指针类型,然后调用它:
- (id)performSelector: (SEL)aSelector { IMP imp = [self methodForSelector: aSelector]; return ((id (*)(id, SEL))imp)(self, aSelector); }
- (id)performSelector: (SEL)aSelector withObject: (id)object { IMP imp = [self methodForSelector: aSelector]; return ((id (*)(id, SEL, id))imp)(self, aSelector, object); }
- (id)performSelector: (SEL)aSelector withObject: (id)object1 withObject: (id)object2 { IMP imp = [self methodForSelector: aSelector]; return ((id (*)(id, SEL, id, id))imp)(self, aSelector, object1, object2); }默认实现
MAObject 提供了一系列方法的默认实现。我们将从 isEqual: 和 hash 的默认实现开始,它们仅使用对象的指针来标识对象:
- (BOOL)isEqual: (id)object { return self == object; }
- (NSUInteger)hash { return (NSUInteger)self; }对于那些对相等性有更宽泛定义的子类,需要重写这些方法;但如果某个子类的对象仅与其自身相等,直接使用这些默认实现即可。
description 方法是另一个需要提供默认实现的便利方法。该实现会生成形如 <MAObject: 0xdeadbeef> 的字符串,其中包含对象的类名和指针值。
- (NSString *)description { return [NSString stringWithFormat: @"<%@: %p>", [self class], self]; }类的标准做法是从其自身的 description 方法中返回类名,因此同样会有一个类方法(class method)从运行时(runtime)中获取该名称并返回:
+ (NSString *)description { return [NSString stringWithUTF8String: class_getName(self)]; }doesNotRecognizeSelector: 是一个较少人知的工具方法。它通过抛出异常来模拟对象实际上并不响应给定选择器(selector)的行为。这在创建强制子类必须实现特定方法的重写点等场景中非常有用:
- (void)subclassesMustOverride { // pretend we don't actually implement this here [self doesNotRecognizeSelector: _cmd]; }代码相当简单。唯一真正棘手的部分在于格式化方法名。我们想要显示类似 -[Class method] 的形式,但类方法需要在前面加 +,如 +[Class classMethod]。为了判断当前属于哪种上下文,代码会检查 isa 是否指向一个元类(metaclass)。如果是,那么 self 就是一个类,应当使用 + 变体;否则,self 是一个实例,使用 - 变体。其余代码仅负责抛出相应的 NSException:
- (void)doesNotRecognizeSelector: (SEL)aSelector { char *methodTypeString = class_isMetaClass(isa) ? "+" : "-"; [NSException raise: NSInvalidArgumentException format: @"%s[%@ %@]: unrecognized selector sent to instance %p", methodTypeString, [[self class] description], NSStringFromSelector(aSelector), self]; }最后,还有一些小方法:它们要么为显而易见的问题提供显而易见的答案(例如 self 方法),要么存在是为了让子类始终能安全地调用 super(例如空的 +initialize 方法),或者作为重写点存在(例如会抛出异常的 copy 实现)。这些方法都没有特别有趣之处,但为了完整性我仍将它们列出:
- (id)self { return self; }
- (BOOL)isProxy { return NO; }
+ (void)load { }
+ (void)initialize { }
- (id)copy { [self doesNotRecognizeSelector: _cmd]; return nil; }
- (id)mutableCopy { [self doesNotRecognizeSelector: _cmd]; return nil; }
- (id)forwardingTargetForSelector: (SEL)aSelector { return nil; }
- (void)forwardInvocation: (NSInvocation *)anInvocation { [self doesNotRecognizeSelector: [anInvocation selector]]; }
+ (BOOL)resolveClassMethod:(SEL)sel { return NO; }
+ (BOOL)resolveInstanceMethod:(SEL)sel { return NO; }结论
NSObject 是一个捆绑了大量不同功能的集合体,但其中并无过于奇异之处。它的主要职责是处理内存分配与管理,使得你能够实际创建对象。它同时为一系列每个对象都需要支持的方法提供了诸多便利的重写点,并将众多运行时函数封装成了更易用的 API。
我跳过了 NSObject 提供的一项重要功能:键值编码(key-value coding)。这一功能相当复杂,值得单独撰文讨论,因此我会在日后另文详述。
今天的讨论到此结束。周五技术问答系列由读者的想法驱动 —— 假如你之前碰巧不知道的话 —— 所以请发送你的主题建议。下次见,别写任何我不会写的代码。
Original (English)
Source: https://www.mikeash.com/pyblog/friday-qa-2013-01-25-lets-build-nsobject.html
The NSObject class lies at the root of (almost) all classes we build and use as part of Cocoa programming. What does it actually do, though, and how does it do it? Today, I’m going to rebuild NSObject from scratch, as suggested by friend of the blog and occasional guest author Gwynne Raskind.
Components of a Root ClassWhat exactly does a root class do? In terms of Objective-C itself, there is precisely one requirement: the root class’s first instance variable must be isa, which is a pointer to the object’s class. The isa is used to figure out what class an object is when dispatching messages. That’s all there has to be, from a strict language standpoint.
A root class that only provides that wouldn’t be very useful, of course. NSObject provides a lot more. The functionality it provides can be broken down into three categories:
-
Memory management: standard memory management methods like retain and release are implemented in NSObject. The alloc method is also implemented there.
-
Introspection: NSObject provides a bunch of methods that are essentially wrappers around Objective-C runtime functionality, such as class, respondsToSelector:, and isKindOfClass:.
-
Default implementations of miscellaneous methods: there are a bunch of methods that we count on every object implementing, such as isEqual: and description. In order to ensure that every object has an implementation, NSObject provides a default implementation that every subclass gets if it doesn’t bring its own.
CodeI’ll be reimplementing NSObject functionality as MAObject. I’ve posted the full code for this article on GitHub:
https://github.com/mikeash/MAObject
Note that this code is built without ARC. Although ARC is great and should be used whenever possible, it really gets in the way when implementing a root class, because a root class needs to implement memory management and ARC prefers that you leave memory management up to the compiler.
Instance VariablesMAObject has two instance variables. The first is the isa pointer. The second is the object’s reference count:
@implementation MAObject { Class isa; volatile int32_t retainCount; }The reference count will be managed using functions from OSAtomic.h to ensure thread safety, which is why it has a somewhat unusual definition rather than just using NSUInteger or similar.
NSObject actually holds reference counts externally. There’s a global table which maps an object’s address to its reference count. This saves memory, because the table represents the common reference count of 1 by not having an entry in the table at all. However, this technique is complex and a bit slow, so I opted not to follow it for my own version.
Memory ManagementThe first thing that MAObject needs to be able to do is to create instances. This is done by implementing the +alloc method. (I’m skipping the deprecated and rarely used +allocWithZone:, which these days does the same thing and ignores its parameter anyway.)
Subclasses rarely override +alloc, and rely on the root class to allocate memory for them. That means that MAObject needs to be able to allocate instances not only of MAObject, but of any subclass. This is done by taking advantage of the fact that the value of self in a class method is the class the message was actually sent to. If code does [SomeSubclass alloc], then self holds a pointer to SomeSubclass. That class can then be used to query the runtime to figure out how much memory to allocate, and to set the isa pointer correctly. The retain count is also initialized to 1, as suits a newly allocated object:
+ (id)alloc { MAObject *obj = calloc(1, class_getInstanceSize(self)); obj->isa = self; obj->retainCount = 1; return obj; }The retain method simply uses OSAtomicIncrement32 to bump up the retain count, and returns self:
- (id)retain { OSAtomicIncrement32(&retainCount); return self; }The release method does a bit more. It first decrements the retain count. If the retain count was decremented to 0, then the object needs to be destroyed, so the code calls dealloc:
- (oneway void)release { uint32_t newCount = OSAtomicDecrement32(&retainCount); if(newCount == 0) [self dealloc]; }The implementation of autorelease calls NSAutoreleasePool to add self to the current autorelease pool. Autorelease pools are part of the runtime these days, so this is a somewhat indirect route, but the autorelease APIs in the runtime are private, so this is the best we can do for now:
- (id)autorelease { [NSAutoreleasePool addObject: self]; return self; }The retainCount method simply returns the value held in the ivar:
- (NSUInteger)retainCount { return retainCount; }Finally, there’s the dealloc method. In normal classes, dealloc needs to clean up any instance variables and then call super. The root class has to actually dispose of the memory occupied by the object itself. In this case, it’s just a simple call to free:
- (void)dealloc { free(self); }There are a couple of helper methods as well. NSObject provides a do-nothing init method for consistency, so that subclasses can always call [super init]:
- (id)init { return self; }There’s also a new method, which is just a wrapper around alloc and init:
+ (id)new { return [[self alloc] init]; }There’s also an empty finalize method. NSObject implements this as part of its garbage collection support. MAObject doesn’t support garbage collection in the first place, but I included this just because NSObject has it:
- (void)finalize { }IntrospectionMany of the introspection methods are just wrappers around runtime functions. Since that’s not too interesting, I’ll give a brief discussion of what the runtime function is doing behind the scenes as well.
The simplest introspection method is class, which just returns the value of isa:
- (Class)class { return isa; }Technically, this method will fail on tagged pointers. A proper implementation should call object_getClass, which behaves correctly for tagged pointers, and extracts the isa from a normal pointer.
The superclass instance method is equivalent to just invoking the superclass class method on the object’s class, so that’s exactly what the method does:
- (Class)superclass { return [[self class] superclass]; }There are also class methods for these. The +class method just returns self, which is the class object. This is a little weird, but it’s how NSObject does things. [obj class] returns the object’s class, but [MyClass class] just returns a pointer to MyClass itself. It’s not consistent, as MyClass also has a class, which is the MyClass metaclass, but it’s how things are done:
+ (Class)class { return self; }The +superclass method does what it says. This is implemented by calling class_getSuperclass, which just grovels around inside the class structure maintained by the runtime and pulls out the pointer to the superclass.
+ (Class)superclass { return class_getSuperclass(self); }There are also methods for querying whether an object’s class matches a particular class. The simple one is isMemberOfClass:, which does a strict check, ignoring subclasses. Its implementation is simple:
- (BOOL)isMemberOfClass: (Class)aClass { return isa == aClass; }The isKindOfClass: method checks subclasses too, so that [subclassInstance isKindOfClass: [Superclass class]] returns YES. The output of this method is essentially the same as that of the class method isSubclassOfClass:, so it just calls through:
- (BOOL)isKindOfClass: (Class)aClass { return [isa isSubclassOfClass: aClass]; }That method gets a bit more interesting. Starting from self, it walks up the class hierarchy, comparing with the target class at each level. If it finds a match, it returns YES. If it runs off the top of the class hierarchy without ever finding a match, it returns NO:
+ (BOOL)isSubclassOfClass: (Class)aClass { for(Class candidate = self; candidate != nil; candidate = [candidate superclass]) if (candidate == aClass) return YES;
return NO; }It’s interesting to note that this check is not particularly efficient. If you call this method on a class that’s deep in the class hierarchy, it can take a lot of loop iterations before it returns NO. Because of that, isKindOfClass: checks can be quite a lot slower than message sends, and can actually be substantial bottlenecks in certain cases. Just one more reason to avoid them when possible.
The respondsToSelector: method just calls through to the runtime function class_respondsToSelector. That, in turn, looks up the selector in the class’s method table to see if it has an entry:
- (BOOL)respondsToSelector: (SEL)aSelector { return class_respondsToSelector(isa, aSelector); }There’s a class method, instancesRespondToSelector:, which is nearly identical. The only difference is passing self, which is the class in this context, rather than isa, which would be the metaclass here:
+ (BOOL)instancesRespondToSelector: (SEL)aSelector { return class_respondsToSelector(self, aSelector); }There are also two conformsToProtocol: methods, one for instances and one for classes. These also just wrap a runtime function, which in this case just consults a table of every protocol that the class conforms to in order to see if the given protocol is present:
- (BOOL)conformsToProtocol: (Protocol *)aProtocol { return class_conformsToProtocol(isa, aProtocol); }
+ (BOOL)conformsToProtocol: (Protocol *)protocol { return class_conformsToProtocol(self, protocol); }Next is methodForSelector:, and its classy cousin instanceMethodForSelector:. These both call through to class_getMethodImplementation, which looks up the selector in the class’s method table and returns the corresponding IMP:
- (IMP)methodForSelector: (SEL)aSelector { return class_getMethodImplementation(isa, aSelector); }
+ (IMP)instanceMethodForSelector: (SEL)aSelector { return class_getMethodImplementation(self, aSelector); }An interesting aspect of these methods is that class_getMethodImplementation always returns an IMP, even for unknown selectors. When the class doesn’t actually implement a method, it returns a special forwarding IMP which wraps up the message arguments starts down the path to invoking forwardInvocation:.
The methodSignatureForSelector: method just wraps the equivalent class method:
- (NSMethodSignature *)methodSignatureForSelector: (SEL)aSelector { return [isa instanceMethodSignatureForSelector: aSelector]; }The class method in turn wraps some runtime calls. It first fetches the Method for the given selector. If it can’t be found, then the class doesn’t implement that method, and this code returns nil. Otherwise, it extracts the C string representing the method’s types, and wraps the in an NSMethodSignature object:
+ (NSMethodSignature *)instanceMethodSignatureForSelector: (SEL)aSelector { Method method = class_getInstanceMethod(self, aSelector); if(!method) return nil;
const char *types = method_getTypeEncoding(method); return [NSMethodSignature signatureWithObjCTypes: types]; }Finally, there’s performSelector:, and the two withObject: variants that take arguments. These aren’t strictly introspection, but they fall in the same general category of wrapping lower-level runtime functionality. They simply retrieve the IMP for the given selector, cast it to the appropriate function pointer type, and call it:
- (id)performSelector: (SEL)aSelector { IMP imp = [self methodForSelector: aSelector]; return ((id (*)(id, SEL))imp)(self, aSelector); }
- (id)performSelector: (SEL)aSelector withObject: (id)object { IMP imp = [self methodForSelector: aSelector]; return ((id (*)(id, SEL, id))imp)(self, aSelector, object); }
- (id)performSelector: (SEL)aSelector withObject: (id)object1 withObject: (id)object2 { IMP imp = [self methodForSelector: aSelector]; return ((id (*)(id, SEL, id, id))imp)(self, aSelector, object1, object2); }Default ImplementationsMAObject provides default implementations of a bunch of methods. We’ll start off with default implementations of isEqual: and hash, which just use the object’s pointer for identity purposes:
- (BOOL)isEqual: (id)object { return self == object; }
- (NSUInteger)hash { return (NSUInteger)self; }Any subclasses with a more expansive notion of equality will have to override these methods, but any subclass where an object is only ever equal to itself can just use these implementations.
The description method is another handy one to have a default implementation. This implementation just generates a string of the form <MAObject: 0xdeadbeef>, containing the object’s class and pointer value.
- (NSString *)description { return [NSString stringWithFormat: @"<%@: %p>", [self class], self]; }The standard for classes is to just return the class name from their own description, so there’s a class method as well that fetches that name from the runtime and returns it:
+ (NSString *)description { return [NSString stringWithUTF8String: class_getName(self)]; }doesNotRecognizeSelector: is a lesser-known utility method. It throws an exception to make it look like the object doesn’t actually respond to the given selector. This is useful for things like creating override points where subclasses have to implement a particular method:
- (void)subclassesMustOverride { // pretend we don't actually implement this here [self doesNotRecognizeSelector: _cmd]; }The code is fairly simple. The only really tricky bit is formatting the method name. We want to display something like -[Class method], but class methods need a + at the front, as in +[Class classMethod]. To figure out which context it’s in, the code checks to see whether isa is a metaclass. If it is, then self is a class, and the + variant should be used. Otherwise, self is an instance, and the - variant is used. The rest of the code just raises the appropriate NSException:
- (void)doesNotRecognizeSelector: (SEL)aSelector { char *methodTypeString = class_isMetaClass(isa) ? "+" : "-"; [NSException raise: NSInvalidArgumentException format: @"%s[%@ %@]: unrecognized selector sent to instance %p", methodTypeString, [[self class] description], NSStringFromSelector(aSelector), self]; }Finally, there are a bunch of little methods that either provide obvious answers to obvious questions (e.g. the self method), exist to let subclasses always safely call super (e.g. the empty +initialize method), or exist as override points (e.g. the copy implementation that throws an exception). None of these are particularly interesting, but I include them for completeness:
- (id)self { return self; }
- (BOOL)isProxy { return NO; }
+ (void)load { }
+ (void)initialize { }
- (id)copy { [self doesNotRecognizeSelector: _cmd]; return nil; }
- (id)mutableCopy { [self doesNotRecognizeSelector: _cmd]; return nil; }
- (id)forwardingTargetForSelector: (SEL)aSelector { return nil; }
- (void)forwardInvocation: (NSInvocation *)anInvocation { [self doesNotRecognizeSelector: [anInvocation selector]]; }
+ (BOOL)resolveClassMethod:(SEL)sel { return NO; }
+ (BOOL)resolveInstanceMethod:(SEL)sel { return NO; }ConclusionNSObject is a big bundle of different functionality, but nothing too strange. Its main function is to handle memory allocation and management so that you can actually create objects. It also provides a bunch of handy override points for methods that every object is expected to support, and wraps a bunch of runtime functions in a nicer API.
I’ve skipped over a big piece of functionality provided by NSObject: key-value coding. This is complex enough that it deserves its own article, so I will come back to that another time.
That’s it for today. Friday Q&A is driven by reader ideas, in case you somehow didn’t already know, so please send in your topic suggestions. Until next time, don’t code anything I wouldn’t code.