NSArray和NSMutableArray的Objective-c内部实现

8

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

所以... NSObject 对象和类都返回相同的copy实现(正如我所预期的)。然而,NSArrayNSMutableArray 对象和类不仅返回不同的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>

一个 NSArray 实例实际上是 NSCFArray 的“桥接”实例(或类似的东西)。实际上是一个子类,尽管我不知道幕后的连线是如何工作的。 - Hot Licks
是的,我开始看到类似的东西了...有趣的是,上面代码中数组对象的类是__NSArrayI和__NSArrayM。但我仍然不确定这能解释那种影响。 - Eamonn Moloney
1个回答

26

所有的实现细节都在这里,这也是一个相对了解的猜测。

首先,并不需要通过这种方式来打印十六进制值,只需执行:

NSLog(@"imp: %p", [NSObject instanceMethodForSelector:@selector(...)]);
请注意,对于一个类对象调用methodForSelector:将返回类方法的IMP。
现在,进入正题。
将Objective-C与其他流行的OO语言(但并非所有语言)区分开来的一件事是,Class对象实际上是元类的实例。该元类 - 除了“元类”之外没有名称 - 正好响应NSObject协议(或多或少)。实际上,它有点像NSObject的派生类。
因此,当您获取NSObject实例和类的某些选择器的实现时,它们通常会相同。请注意,这就是允许Class成为NSDictionary中键的原因;类可以复制。具体而言,NSObjectcopy方法只需执行 return [self retain];,当然,由于它们(几乎总是)作为单例静态编译到二进制文件中,所以retain操作对类而言是无意义的。嗯,从技术上讲,copy调用copyWithZone:,后者执行 return [self retain];(这就是为什么即使区域已弃用,也必须子类化copyWithZone:的原因)。
现在,就像Hot Licks指出的那样,NSArray是一个类群,其内部实现细节在过去的几个版本中已更改。过去,所有实例都与配置不同的NSCFArray桥接。在最近的版本中,您会看到与不可变和可变实例相对应的__NSArrayI__NSArrayM实例。大多数情况下,除此之外还有更多的内容,但这是很典型的。 objectAtIndex:的实例方法在这两个类之间不同,这在类群中是典型的。类群的目的是提供具有一堆在基本接口上实现的方法的原始接口(这就是为什么头文件在核心@interface和一系列分类@interfaces之间分开的原因;基类中的分类完全是根据核心API实现的)。
在内部,集群中公开声明的类有具体子类。这些具体的子类高度优化以执行特定任务 - 在这种情况下为不可变vs.可变数组存储 - 其中子类重写了广告出来的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,该IMP会产生一个漂亮的错误,指示您尝试通过迂回的方式使用目标对象(在本例中为元类实例)无法响应的选择器。这就是为什么instanceMethodForSelector:文档中说明,在可能存在关于目标是否实现选择器的问题的情况下,应先使用respondsToSelector:的原因。

(好吧,这比预期的要多一点...但希望仍然有用!)


请纠正我如果我错了,但我相当确定 NSObjectcopy 方法实际上是执行 return [self copyWithZone:nil](或者它可能会获取一个特定的区域来使用)。像许多其他类一样,元类实现它为 return [self retain] - Mike Abdullah
是的 - Mike 是对的...现在通过 copyWithZone: 进行了额外的方法调用,但已经不再执行任何操作。已修复。 - bbum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接