问题
在我的 ARC 项目中,我有一个管理对象的类,称为LazyMutableArray
. 有些对象实际上是 nil,但我收藏的用户永远不会知道这一点;因此我将它设为 的子类NSMutableArray
,并尝试做“同样的事情”。特别是,对象在添加时会被保留。
现在让我们看一下其他方法的内存行为。事实证明,AppleNSArray
将销毁方法记录为该规则的一个例外,因为它们是release,而不是autoreleased对象。
addObject:
关于++objectAtIndex:
数组破坏的组合是否被 Apple 记录为永远不会自动释放,或者只是恰好出现在我测试的示例和 Apple 包含的示例中,存在一些争论。
如何在我的子类中创建具有完全相同内存语义的方法?
最后更新
经过一番思考,我决定NSMutableArray
在这种情况下,与NSPointerArray
. 我应该注意,新类与之前的实现具有相同的retain
/autorelease
对。
感谢 Rob Napier,我发现对我的objectAtIndex:
方法的任何修改都不会改变这种行为,这回答了我最初关于这种方法的问题。
在实践层面,一些人说任何方法都可以无缘无故地解决额外的retain
/对;autorelease
以其他方式期望是不合理的,试图找出哪些方法可以做到这一点,哪些不可以做到这一点是不合理的。因此,在多个层面上,这对我来说都是一个很好的学习机会。
代码(基于NSMutableArray
)可在 GitHub 上找到: implementation、header、test(that's -testLazyMutableMemorySemantics
)。谢谢大家的参与。
为什么我尝试子类化NSMutableArray
:
我同意,子类化基础对象并不总是一个合适的解决方案。在这种情况下,我有对象(实际上是 OData 资源),其中大多数都有subobjects。子对象数组最自然的类显然是NSArray
. 使用不同的课程对我来说似乎没有意义。
但是对于 OData 集合,这个“子对象数组”虽然是一个 NSArray,但必须有不同的实现。具体来说,对于 1000 个元素的集合,鼓励服务器以(比如说)20 个为单位返回集合,而不是一次全部返回。如果在这种情况下有另一种合适的模式,我会全神贯注。
我如何找到这个的更多细节
我对这个集合进行了单元测试,可以将值放入数组中,从数组中读取,等等。到目前为止,一切都很好。但是,我意识到返回对象会增加其保留计数。
我怎么看?假设我在惰性数组中插入了两个对象lazy
,一个弱保留,一个强保留(*参见代码*)。然后按预期保留计数为weakSingleton
1。但现在我阅读了元素:
XCTAssertEqual(weakSingleton, lazy[0], @"Correct element storage"); // line B
在调试器中,我看到保留计数上升到 2。当然,-retainCount
可能会给我错误的信息,所以让我们尝试通过以下方式破坏数组中的引用
lazy[0] = nil; // yep, does the right thing
XCTAssertNil(weakSingleton, @"Dropped by lazy array"); // line C <-- FAIL
确实,我们看到weakSingleton
没有发布。
现在你可能猜到它不仅仅是一个保留,它是一个自动释放的保留——在 B 行周围放置一个@autorelease
weakSingleton
释放. 这对的确切来源并不明显,但似乎来自NSPointerArray -addPointer:
(不幸的是不是来自 ARC's [[object retain] autorelease]
)。但是,我不想返回一个自动释放的对象并使方法语义与其超类不同!
毕竟,我要覆盖的方法 NSMutableArray -objectAtIndex:` 并没有这样做;如果数组被释放,它返回的对象将立即释放,如 Apple 的示例中所述。这就是我想要的:修改 A 行周围的方法,使其返回的对象没有额外的保留/自动释放对。我不确定编译器是否应该让我这样做:)
注意 1我可以为单个文件关闭 ARC,但这将是我的第一个非 ARC Objective-C 代码。无论如何,这种行为可能不是来自 ARC。
注2有什么大惊小怪的?好吧,在这种情况下,我可以更改我的单元测试,但事实是,通过添加或删除 B 行,我正在更改 C 行的单元测试结果。
换句话说,我的方法所描述的行为[LazyMutableArray -objectAtIndex]
本质上是通过读取索引 0 处的对象,我实际上是在更改该对象的保留计数,这意味着我可能会遇到意外的错误。
注意 3当然,如果对此不采取任何措施,我将记录此行为并继续;也许,这确实应该被视为一个实现细节,而不是包含在测试中。
实现的相关方法
@implementation LazyMutableArray {
NSPointerArray *_objects;
// Created lazily, only on -setCount:, insert/add object.
}
- (id)objectAtIndex:(NSUInteger)index {
@synchronized(self) {
if (index >= self.count) {
return nil;
}
__weak id object = [_objects pointerAtIndex:index];
if (object) {
return object;
}
}
// otherwise do something else to compute a return value
// but this branch is never called in this test
[self.delegate array:self missingObjectAtIndex:index];
@synchronized(self) {
if (index >= self.count) {
return nil;
}
__weak id object = [_objects pointerAtIndex:index];
if (object) {
return object;
}
}
@throw([NSException exceptionWithName:NSObjectNotAvailableException
reason:@"Delegate was not able to provide a non-nil element to a lazy array"
userInfo:nil]);
}
- (void)createObjects {
if (!_objects) {
_objects = [NSPointerArray strongObjectsPointerArray];
}
}
- (void)addObject:(id)anObject {
[self createObjects];
[_objects addPointer:(__bridge void*)anObject];
}
完整的测试代码:
// Insert two objects into lazy array, one held weakly, one held strongly.
NSMutableArray * lazy = [LazyMutableArray new];
id singleton = [NSMutableArray new];
[lazy addObject:singleton];
__weak id weakSingleton = singleton;
singleton = [NSMutableDictionary new];
[lazy addObject:singleton];
XCTAssertNotNil(weakSingleton, @"Held by lazy array");
XCTAssertTrue(lazy.count == 2, @"Cleaning and adding objects");
// @autoreleasepool {
XCTAssertEqual(weakSingleton, lazy[0], @"Correct element storage");
XCTAssertEqual(singleton, lazy[1], @"Correct element storage");
// }
lazy = nil;
XCTAssertNotNil(singleton, @"Not dropped by lazy array");
XCTAssertNil(weakSingleton, @"Dropped by lazy array");
最后一行失败,但如果我将第一行更改为lazy = [NSMutableArray new]
或取消注释,它会成功@autoreleasepool
。