質問
私の ARC プロジェクトには、 というオブジェクトを管理するクラスがありますLazyMutableArray
。一部のオブジェクトは実際には nil ですが、私のコレクションのユーザーはこれを知ることはありません。したがって、私はそれを のサブクラスにしNSMutableArray
、「同じこと」をしようとします。特に、オブジェクトは追加時に保持されます。
次に、他のメソッドのメモリ動作を見てみましょう。NSArray
破壊メソッドは、自動解放されたオブジェクトではなく解放するという点で、 Apple によってこの規則の例外であると文書化されていることが判明しました。
addObject:
++objectAtIndex:
配列の破壊の組み合わせがApple によって文書化されており、決して自動解放されないのか、それとも私がテストした例と Apple が含まれている例にたまたま含まれているのかについては、いくつかの議論があります。
サブクラスでまったく同じメモリ セマンティクスを持つメソッドを作成するにはどうすればよいですか?
最後の更新
NSMutableArray
少し考えた結果、この場合は に比べて に基づく実装がより適切であると判断しましたNSPointerArray
。retain
新しいクラスには、以前の実装と同じ/autorelease
ペアがあることに注意してください。
Rob Napier のおかげで、私のobjectAtIndex:
メソッドを変更しなくてもこの動作が変わることがわかりました。これは、このメソッドに関する最初の質問に答えています。
実用的なレベルでは、何人かの人々は、どの方法でも余分なretain
/autorelease
ペアに理由なく取り組むことができると言いました。そうでないことを期待するのは合理的ではありません。したがって、それは私にとっていくつかのレベルで素晴らしい学習の機会でした.
コード ( に基づくNSMutableArray
) は GitHub で入手できます: implementation、header、test (つまり-testLazyMutableMemorySemantics
)。ご参加いただきありがとうございました。
サブクラス化しようとする理由NSMutableArray
:
基本オブジェクトをサブクラス化することは、常に適切な解決策であるとは限りません。この場合、オブジェクト (実際には OData リソース) があり、そのほとんどにサブオブジェクトがあります。サブオブジェクトの配列の最も自然なクラスは明らかにNSArray
です。別のクラスを使用しても意味がないようです。
しかし、OData コレクションの場合、この「サブ オブジェクトの配列」は NSArray であるため、別の実装が必要です。具体的には、1000 個の要素のコレクションの場合、サーバーは一度にすべてではなく、(たとえば) 20 個のバッチでコレクションを返すことが推奨されます。この場合に適切な別のパターンがあれば、私はすべて耳にします。
これを見つけた方法の詳細
このコレクションを徹底的に単体テストすると、値を配列に入れたり、配列から読み取ったりすることができます。ここまでは順調ですね。ただし、オブジェクトを返すと保持カウントが増加することに気付きました。
どうやって見るの?2 つのオブジェクトを lazy array に挿入するとしますlazy
。1 つは弱く保持され、もう 1 つは強く保持されます (*コードを参照してください *)。その後、保持カウント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 からではなく[[object retain] autorelease]
) に由来するようです。ただし、自動解放されたオブジェクトを返して、メソッドのセマンティクスをそのスーパークラスとは異なるものにしたくはありません!
結局、私がオーバーライドしているメソッド NSMutableArray -objectAtIndex:` はそれを行いません。返されるオブジェクトは、Apple の例に示されているように、配列が解放されるとすぐに解放されます。それが私が望むことです:それが返すオブジェクトが余分な保持/自動解放ペアを持たないように、行 A の周りのメソッドを変更します。コンパイラが私にそれをさせてくれるかどうかさえわかりません:)
注 1 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
ます。