将非NSObject对象添加到NSMutableArray

13

最近的 Stack Overflow 讨论 让我感到困惑。NSMutableArray 的 addObject: 原型是

- (void)addObject:(id)anObject

同时id在objc.h中被定义为

typedef struct objc_class *Class;
typedef struct objc_object {
    Class isa;
} *id; 

当我把一个NSObject或它的子类加入到NSMutableArray中时,它的保留计数会被递增;而当我从NSMutableArray中移除它时,保留计数会被递减。这是否意味着如果将一个非NSObject或其子类的id类型添加到NSMutableArray中,它必须响应保留和释放消息?id的定义似乎并没有强制要求这样做。这是一个Objective-C指令,任何id类型都应该响应标准内存管理消息吗?


为什么要使用一个不派生自NSObject的对象? - semisight
我并不需要,我只是对NSMutableArray(以及所有可变集合)内部的内存管理方式感兴趣。 - jbat100
虽然它并没有回答你的问题,但是 retainrelease 实际上是在 NSObject 协议中声明的,而不是在 NSObject 接口中声明的,因此从 NSObject 继承并不是必要的。 - Matt Wilding
6个回答

7
大多数Foundation容器(以及绝大多数由苹果开发的类和第三方开发的类)的一个艰难的事实是,当一个方法接受id类型时,它实际上应该读作id,这意味着任何响应NSObject协议的类型。那些不属于NSObject层次结构的类的实例不太可能响应-retain和-release,这在尝试将它们添加到容器中时特别不方便。它们也不太可能响应-hash,-isEqual:,-description,-copy以及Foundation容器可以用于其内容的所有其他方法,出于任何原因。
例如,如果您尝试将Class对象添加到Foundation容器中(除了NSMapTable之外,因为后者的设计非常灵活),您将遇到困境,因为现代ObjC类被期望继承自NSObject,或至少实现NSObject协议。
虽然这种情况相当罕见,但Class几乎是唯一一个有用的类,它没有继承自NSObject。

4
这是否意味着,如果向NSMutableArray中添加非NSObject或其子类的id类型对象,它必须响应retain和release消息?
默认情况下,是的,尽管可以使用CF API来解决此问题。
id的定义似乎没有强制要求。这是一种Objective-C指令,任何id类型都应该响应标准内存管理消息吗?
这只是库编写方式而已。根类(不继承自NSObject)非常不寻常。一个替代方案可能是- (void)addObject:(id<NSObject>),但那会要求您的根类进行大量扩展...也许更好的解决方案是一个协议NSReferenceCounted,从NSObject中提取相关部分。
然而,NS集合类型确实假定它们正在处理NSObjects(例如,字典使用hashdescription)。

1
尽管zneak指出集合使用其他与内存管理无关的消息(-hash,-isEqual:,-description,-copy),但NSReferenceCounted协议的想法不错。因此,也许一个NSCollectable协议会是对Objective-C未来的明智调整。感谢您的答案。 - jbat100

2
不,没有任何约定规定“id”类型的对象应该响应retain/release消息;实际上,可以说保证这些方法的存在是NSObject协议(而不是类)的目的。但是,“id”会告诉编译器“不要费心进行类型检查”,因此当您将一个不实现这些方法的对象添加到nsarray中时,它将编译,但您将在运行时崩溃。请参见http://unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs-id.html以获取更详细的解释。

0

你不应该将非对象类型放入 NSArray 中,例如如果你想存储一个 CGRects 数组,那么你需要将它们包装在 NSValue 对象中并存储它们(对于 CGPoint, CGSize 等也是如此)。

如果你有一个自定义结构体,那么你需要一个 NSObject 的派生包装器,这在第一次就有点违背了初衷!


2
我知道并且曾经在几年前经历过这种痛苦,我只是想知道为什么addObject:的原型是- (void)addObject:(id)anObject,而不是- (void)addObject:(id<NSObject>)anObject;。 - jbat100
@jbat100 完全同意,这很具有误导性! - Simon Lee
我认为他并不是在询问如何将C结构体放入数组中,而是在询问如何将不是从NSObject(例如NSProxy)继承的id放入其中。 - Matt Wilding
NSProxy符合NSObject,所以那可能是一个不好的例子。 - Simon Lee
@Simon Lee,是的,它可以,但这是我能想到的第一个。 - Matt Wilding
3
历史原因导致 <NSObject> 直到 NSArray 的接口已经被写成可以接收 (id) 参数后才真正发挥了作用。当时的错误认识是可能还有其他根类符合集合类兼容性,而这一点并没有被证明正确。多年的经验已经证明这个假设是不正确的。 - bbum

0
如果您真的需要一个不想保留/释放其项目的数组,您可能希望使用CFArrayCreateMutable创建它,并传递适当的回调函数。(CFMutableArray是桥接到NSMutableArray的。)

0
除了上面的技术讨论,我认为答案与语言的历史以及其对惯例而非语法的偏好有关。正式协议最初并不是该语言的一部分 - 一切都是非正式的。苹果随后大多转向了正式协议,但非正式协议是该语言的一部分,并且被官方API的某些部分使用。因此,它们是Objective-C的完全支持的、一流的组成部分。
如果您查看NSArray的文档,其中包括以下内容:
在大多数情况下,您的自定义NSArray类应符合Cocoa的对象所有权约定。因此,您必须向添加到集合中的每个对象发送retain消息,并向从集合中删除的每个对象发送release消息。当然,如果子类化NSArray的原因是实现与规范不同的对象保留行为(例如,非保留数组),则可以忽略此要求。
还有:
NSArray与其Core Foundation对应项CFArray之间是“无缝桥接”的,有时您可以通过CFArray轻松完成无法使用NSArray完成的任务。例如,CFArray提供了一组回调函数,其中一些是用于实现自定义保留-释放行为的。如果您为这些回调指定NULL实现,则可以轻松获得非保留数组。
因此,您的问题基于错误前提,具体而言,可能是部分阅读文档或不完全考虑语言。
id 未被正确使用,因为NSObject协议与对象必须实现以便与NSArray一起使用的协议不匹配。相反,在文档中建立了一个非正式协议(尽管在第二次去除非正式性的情况下,这是罕见的),它恰好是NSObject的子集。尽管非正式协议越来越少见,但它们是有效的,但并不例外。
因此,我的简短回答是:NSArray记录了一个非正式协议,该协议与NSObject不同。非正式协议在语法中未表示,因此使用id。

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