gpt4 book ai didi

objective-c - 如何在子类中复制NSArray内存语义

转载 作者:搜寻专家 更新时间:2023-10-30 19:43:58 31 4
gpt4 key购买 nike

问题
在我的arc项目中,我有一个管理对象的类,名为LazyMutableArray。有些对象实际上是nil,但我的集合的用户永远不会知道这一点;因此,我将它设为NSMutableArray的子类,它尝试执行“相同的操作”。特别是,对象在添加时被保留。
现在让我们看看其他方法的内存行为。原来NSArray销毁方法是documented by Apple的一个例外,因为它们释放的不是自动释放的对象。
关于addObject:+objectAtIndex:+数组销毁的组合是否被苹果记录为永远不会自动删除,或者只是碰巧出现在我测试的示例和苹果包含的示例中,存在一些争论。
如何在子类中创建具有完全相同内存语义的方法?
最后更新
经过一番思考,我决定在这种情况下,基于NSMutableArray的实现比基于NSPointerArray的实现更合适。我要注意的是,新的类与前一个实现具有相同的retain/autorelease对。
多亏了rob napier,我发现对objectAtIndex:方法的任何修改都不会改变这种行为,这回答了我最初关于这种方法的问题。
在实践层面上,一些人说任何方法都可以无缘无故地处理一对额外的retain/autorelease对;期望其他方法是不合理的,试图找出哪些方法做到了这一点,哪些没有做到这一点也是不合理的。这对我来说是一个很好的学习机会。
代码(基于NSMutableArray)可在github获得:implementationheadertest(即-testLazyMutableMemorySemantics)。
谢谢大家的参与。
为什么我试图将NSMutableArray子类化:
我同意,子类化基础对象并不总是一个合适的解决方案。在这种情况下,我有对象(事实上,odata资源),其中大多数都有子对象。子对象数组最自然的类显然是NSArray。在我看来,换一个班似乎没什么意义。
但是对于odata集合,这个“子对象数组”作为nsarray,必须有不同的实现。具体地说,对于1000个元素的集合,鼓励服务器以批(比如)20的形式返回集合,而不是一次返回所有集合。如果有其他模式适合这种情况,我洗耳恭听。
我是怎么找到这个的
我对这个集合进行了单元测试,可以将值放入数组,从数组中读取,等等。到目前为止,还不错。但是,我意识到返回对象会增加其保留计数。
我怎么看?假设我在lazy数组中插入两个对象,一个弱保持,一个强保持(参见代码*)。然后按预期,retain count oflazy为1。但现在我读到元素:

XCTAssertEqual(weakSingleton, lazy[0], @"Correct element storage"); // line B

在调试器中,我看到retain计数增加到2。当然, weakSingleton可能会给我错误的信息,所以让我们尝试通过
lazy[0] = nil; // yep, does the right thing
XCTAssertNil(weakSingleton, @"Dropped by lazy array"); // line C <-- FAIL

实际上,我们看到 -retainCount没有被释放。
现在你可能已经猜到了,它不仅仅是一个retain,它是一个自动释放的retain——在b行周围放置一个 weakSingleton来释放 @autorelease。这一对的确切来源并不明显,但似乎来自 weakSingleton(不幸的是,并非来自arc的 NSPointerArray -addPointer:)。但是,我不想返回一个自动释放的对象,并使方法语义不同于它的超类!
毕竟,我要重写的方法nsmutablearray-objectatindex:`,不会这样做;如果释放一个数组,它返回的对象将立即释放loc,如苹果示例中所述。这就是我想要的:修改a行周围的方法,使它返回的对象没有额外的retain/autorelease对。我不确定编译器是否应该让我这么做:)
注1:我可以为单个文件关闭arc,但这将是我的第一个非arc objective-c代码。在任何情况下,行为都可能不是来自arc。
注意2有什么好大惊小怪的?在这种情况下,我可以更改单元测试,但事实上,通过添加或删除行b,我将更改行c的单元测试结果。
换句话说,我的方法 [[object retain] autorelease]所描述的行为本质上是,通过读取索引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");

最后一行失败,但如果我将第一行更改为 [LazyMutableArray -objectAtIndex]或取消注释 lazy = [NSMutableArray new],则成功。

最佳答案

首先,我不会做这个子类。这正是NSPointerArray的作用。把它包装成 掩盖了这种方法可以打破的重要细节。例如,如果 NSArray包含空值,那么 [NSArray arrayWithArray:lazyMutableArray]的正确行为是什么?假设 lazyMutableArray永远不能包含NULL的算法需要警惕这一事实。确实,你可以得到类似的处理不保留的 NSArray作为 CFArray;我从经验中说这就是为什么这种子类非常危险(为什么我几年前就不再这么做了)。不要创建一个子类,该子类不能在其超类可以使用的所有情况下使用( LSP)。
如果您有一个具有新语义的集合,我将从 NSArray对其进行子类化,并使其符合 NSObject。看看 <NSFastEnumeration>如何不是 NSPointerArray的子类。这不是意外。面对同样的问题,注意苹果选择的方向。
到目前为止,你可能猜到它不仅仅是一个保留,它是自动保存的——在B行周围放置一个@ AutoReleLoad释放弱SuntLon。这似乎是因为A行的下弧转换为[[对象保留]自动释放]。但是,我不想返回一个自动释放的对象并让调用者记住这一点!
打电话的人不应该有其他的想法。调用方从不自由地假定方法不添加平衡的自动删除。如果来电者希望自动释放池耗尽,那是他们的责任。
所有这些都是说,如果不需要额外的自动操作,有一些好处,这是一个有趣的学习机会。
我将首先把这段代码简化为最简单的形式,完全不使用您的子类。只需探索 NSArray的工作原理:

__weak id weakobject;
@autoreleasepool
{
NSPointerArray *parray = [NSPointerArray strongObjectsPointerArray];
{
id object = [NSObject new];
[parray addPointer:(__bridge void*)object];
weakobject = object;
}
parray = nil;
}
NSAssert(!weakobject, @"weakobject still exists");

我在这里的结构(比如额外的嵌套块)是为了避免意外地创建我不想创建的强引用而设计的。
在我的实验中,如果没有autoreleasepool,这个方法就会失败。这表示正在添加额外的retain/autorelease,或者通过调用 NSPointerArray,而不是通过arc修改接口。
如果你不使用这个例子,我会有兴趣深入挖掘。这是一个很有趣的问题,即使我不认为你应该用这种方式进行子类化。

关于objective-c - 如何在子类中复制NSArray内存语义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19883056/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com