问题
在我的ARC项目中,我有一个管理对象的类,名为LazyMutableArray。有些对象实际上是零,但是我的集合的用户永远不会知道这一点;因此,我把它作为NSMutableArray的子类,它试图做“同样的事情”。特别是,当添加时,对象被保留。
现在让我们来看看其他方法的内存行为。事实证明,NSArray销毁方法是苹果公司记录 ,是这个规则的一个例外,因为它们释放而不是自动释放对象。
对于addObject: + objectAtIndex: +数组销毁的组合是否被苹果记录为永远不会自动释放,或者只是碰巧出现在我测试的示例和苹果包含的示例中,存在着一些争论。
如何在子类中创建一个内存语义完全相同的方法?
上次更新
经过一些思考,我认为在这种情况下,基于NSMutableArray的实现比NSPointerArray更合适。我要指出的是,新类的retain/autorelease对与前面的实现相同。
多亏了Rob,我发现对objectAtIndex:方法的任何修改都不会改变这种行为,这就回答了我最初提出的关于这个方法的问题。
在实践中,有几个人说,任何方法都可以无缘无故地处理额外的retain/autorelease对;不合理的期望是不合理的,也不合理地试图找出哪些方法这样做,哪些方法不这样做。因此,这对我来说在几个层面上都是一个很好的学习机会。
代码(基于NSMutableArray)可在GitHub上获得:实现、标题、测试 (即-testLazyMutableMemorySemantics)。谢谢大家的参与。
为什么我尝试子类NSMutableArray
子类基础对象,我同意,并不总是一个合适的解决方案。在这种情况下,我有对象(实际上是OData资源),其中大多数都有子对象。最自然的类--子对象数组--显然是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 的发布了这对的确切来源并不明显,但似乎来自NSPointerArray -addPointer: (不幸的不是来自ARC的[[object retain] autorelease])。但是,我不想返回一个自动释放的对象,并使方法语义不同于它的超类!
毕竟,我正在重写的方法NSMutableArray -objectAtIndex:`没有这样做;如果释放了一个数组,它返回的对象将立即去分配,就像苹果的例子中提到的那样。这就是我想要的:修改A行周围的方法,这样它返回的对象就不会有一个额外的returns /autorelease对。我不确定编译器是否应该让我这样做:)
Note 1 I可以为单个文件关闭ARC,但这将是我的第一个非ARC目标C代码。无论如何,这种行为可能不是ARC的行为。
注2有什么大惊小怪?在这种情况下,我可以更改我的单元测试,但事实上,通过添加或删除B行,我正在更改C行的单元测试结果。
换句话说,我的方法[LazyMutableArray -objectAtIndex]所描述的行为本质上是,通过在索引0处读取一个对象,我实际上改变了这个对象的保留计数,这意味着我可能会遇到意外的bug。
当然,Note 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 ),则会成功。
发布于 2013-11-09 23:31:05
首先,我不会做这个子类。这正是NSPointerArray的作用所在。将其封装到NSArray中会掩盖这种方法可能破坏的重要细节。例如,如果[NSArray arrayWithArray:lazyMutableArray]包含NULL,那么lazyMutableArray的正确行为是什么?假设NSArray永远不能包含NULL的算法需要警惕这样一个事实:这个算法可以。的确,您可能会遇到类似的问题,将不保留的CFArray视为NSArray;我从经验中了解到,这正是这类子类可能非常危险的原因(以及我多年前停止这样做的原因)。不要创建一个子类,不能在每种情况下都使用它的超类(LSP)。
如果您有一个具有新语义的集合,我将将其从NSObject中子类化,并使其符合<NSFastEnumeration>。看看NSPointerArray如何不是NSArray的子类。这不是意外。面对同样的问题,请注意苹果选择的方向。
到现在为止,您可能已经猜到,这不仅仅是一个保留,而是一个自动释放的保留--在B行周围放置一个@ autoreleased来释放weakSingleton。这似乎是因为ARC下的A行转换为[对象保留自动释放]。但是,我不想返回一个自动释放的对象并让调用者记住这一点!
打电话的人不应该再假设其他的事情了。调用方从来没有自由地假设方法没有添加平衡的自动释放。如果调用者希望自动释放池耗尽,这是他们的责任。
尽管如此,如果不需要的话,避免额外的自动发布也有一些好处,这是一个有趣的学习机会。
首先,我将这段代码简化为最简单的形式,完全没有子类。只需探索NSPointerArray是如何工作的:
__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");我这里的结构(例如额外的嵌套块)的设计是为了避免意外地创建我不想做的强引用。
在我的实验中,在没有自动释放池的情况下,这是失败的,并且成功了。这意味着额外的保留/自动发布是通过调用addPointer:来添加的,而不是通过修改界面来添加的。
如果您没有将此实现用于addObject:,我将有兴趣深入研究。这是一个有趣的问题,即使我不认为你应该这样细分。
发布于 2013-11-09 23:01:51
我将详细解释为什么我说这“看起来很像一个家庭作业”。这可能会为我赢得很多选票,但它也将作为一个很好的学习案例,为其他人谁后来发现这个问题。
子类化NSMutableArray不是程序的目标。这是实现其他目标的一种手段。如果我想猜测一下,我想您是在尝试创建一个数组,当访问对象时,它会懒洋洋地创建对象。有更好的方法来做到这一点,而不需要自己处理内存管理。
下面是一个示例,说明如何实现延迟加载数组。
@interface LazyMutableArray : NSMutableArray
- (id)initWithCreator:(id(^)(int))creator;
@end
@interface LazyMutableArray ( )
@property (nonatomic, copy) id (^creator)(int);
@property (nonatomic, assign) NSUInteger highestSet;
@end
@implementation LazyMutableArray
- (id)initWithCreator:(id(^)(int))creator
{
self = [super init];
if (self) {
self.highestSet = NSNotFound;
self.creator = creator;
}
return self;
}
- (id)objectAtIndex:(NSUInteger)index
{
id obj = nil;
if ((index < self.highestSet) && (self.highestSet != NSNotFound)) {
obj = [super objectAtIndex:index];
if ([obj isKindOfClass:[NSNull class]]) {
obj = self.creator(index);
[super replaceObjectAtIndex:index withObject:obj];
}
} else {
if (self.highestSet == NSNotFound) {
self.highestSet = 0;
}
while (self.highestSet < index) {
[super add:[NSNull null]];
self.highestSet += 1;
}
obj = self.creator(index);
[super add:obj];
self.highestSet += 1;
}
return obj;
}公平警告:我没有编译或语法检查任何这些代码。它可能有几个bug,但它通常应该能工作。此外,该实现缺少add:、count、removeObjectAtIndex:、insertObject:atIndex:和可能还有replaceObjectAtIndex:withObject:的实现。我在这里展示的只是为了让你开始。
https://stackoverflow.com/questions/19883056
复制相似问题