8

对于所有进入 Objective-c 内部的人来说,这是一个非常有趣的问题......

所以......返回和对象和类NSObject的相同实现(如我所料)。copy但是,NSArray不仅NSMutableArray返回objectAtIndex:对象和类的不同实现,而且每个对象都有不同的实现。

有谁知道为什么下面的代码会产生这种行为?...(至少 和 的类实现NSArrayNSMutableArray相同的:))

NSObject *obj = [[[NSObject alloc] init] autorelease];
NSLog(@"NSObject instance %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod(object_getClass(obj), @selector(copy)))]);
NSLog(@"NSObject class %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod([NSObject class], @selector(copy)))]);

NSArray *array = [[[NSArray alloc] init] autorelease];
NSLog(@"NSArray instance %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod(object_getClass(array), @selector(objectAtIndex:)))]);
NSLog(@"NSArray class %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod([NSArray class], @selector(objectAtIndex:)))]);

NSMutableArray *array1 = [[[NSMutableArray alloc] init] autorelease];
NSLog(@"NSMutableArray instance %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod(object_getClass(array1), @selector(objectAtIndex:)))]);
NSLog(@"NSMutableArray class %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod([NSMutableArray class], @selector(objectAtIndex:)))]);

日志

2012-11-06 16:35:22.918 otest[71367:303] NSObject instance <c0fa7200>
2012-11-06 16:35:23.757 otest[71367:303] NSObject class <c0fa7200>
2012-11-06 16:35:30.348 otest[71367:303] NSArray instance <809a9b00>
2012-11-06 16:35:31.121 otest[71367:303] NSArray class <70bfa700>
2012-11-06 16:35:33.854 otest[71367:303] NSMutableArray instance <f05f9a00>
2012-11-06 16:35:34.824 otest[71367:303] NSMutableArray class <70bfa700>
4

1 回答 1

26

实施细节,全部。因此,这是一个相对充分的猜想。

首先,不需要跳过这样的箍来打印十六进制值,只需执行以下操作:

NSLog(@"imp: %p", [NSObject instanceMethodForSelector:@selector(...)]);

请注意,调用methodForSelector:类对象会返回类方法的 IMP。

现在,谈到手头的问题。

将 Objective-C 与其他流行的 OO 语言(但不是全部)区分开来的一件事是 Class 对象实际上是 Metaclass 的实例。那个元类——实际上除了“元类”之外没有名字——碰巧响应了NSObject协议(或多或少)。事实上,它有点像NSObject.

因此,当您NSObject为实例和类获得某些选择器的实现时,它们通常是相同的。请注意,这是允许 Class 成为 NSDictionary 中的键的原因;类可以复制。 NSObjectcopy方法只是这样做return [self retain];,当然,retain在一个类上是无操作的,因为它们[几乎总是]作为单例静态编译到二进制文件中。好吧,从技术上讲,确实可以copy调用(这就是即使不推荐使用区域,您也必须进行子类化的原因)。copyWithZone:return [self retain];copyWithZone:

现在,正如 Hot Licks 所指出的,NS*Array是一个类集群,其内部实现细节在最近几个版本中发生了变化。过去,所有实例都桥接到NSCFArray配置不同的实例。在最近的版本中,您将看到(原文如此)__NSArrayI__NSArrayM对应于不可变和可变实例的实例。大多数情况下——还有更多的事情发生,但这是非常典型的。

objectAtIndex:两个类之间的实例方法不同是类集群的典型特征。集群的目的是提供一个原始接口,其中包含一堆根据该原始接口实现的方法(这就是为什么标头在核心@interface和一系列分类之间划分的原因@interfaces;基类中的类别完全实现在核心 API 条款)。

在内部,集群中有公开声明的类的具体子类。这些具体的子类针对特定任务进行了高度优化——在这种情况下,不可变与可变数组存储(但可能有更多针对不同目的优化的非公共子类)——其中子类覆盖广告 API 以提供高度各种方法的优化版本。

因此,您在这两个类中看到的是objectAtIndex:针对可变和不可变情况进行优化的不同实现。

现在,为什么类方法相同?好问题。让我们尝试调用它:

((void(*)(id,SEL,int))[[NSArray class] methodForSelector: @selector(objectAtIndex:)])([NSArray class], @selector(objectAtIndex:), 0);


2012-11-06 09:18:23.842 asdfasdf[17773:303] *** Terminating app due to uncaught
   exception 'NSInvalidArgumentException', reason: '+[NSArray objectAtIndex:]:
   unrecognized selector sent to class 0x7fff7563b1d0'

啊哈!因此,您要求实现一种方法,该方法在调用时会引发“无法识别此方法”异常。实际上:

NSLog(@"%@", 
 [NSArray class] respondsToSelector:@selector(objectAtIndex:)] ? @"YES" : @"NO");

2012-11-06 09:24:31.698 asdfasdf[17839:303] NO

看起来运行时正在返回一个 IMP,它会发出一个很好的错误,表明您尝试 - 通过迂回方式 - 使用目标对象(在本例中为元类实例)不响应的选择器。这就是为什么在可能对目标是否实现选择器存在疑问的情况下instanceMethodForSelector:,您应该事先使用的状态的文档。respondsToSelector:

(嗯,这变成了一本书而不是预期......希望仍然有用!)

于 2012-11-06T17:27:52.703 回答