4

質問

私の ARC プロジェクトには、 というオブジェクトを管理するクラスがありますLazyMutableArray。一部のオブジェクトは実際には nil ですが、私のコレクションのユーザーはこれを知ることはありません。したがって、私はそれを のサブクラスにしNSMutableArray、「同じこと」をしようとします。特に、オブジェクトは追加時に保持されます。

次に、他のメソッドのメモリ動作を見てみましょう。NSArray破壊メソッドは、自動解放されたオブジェクトではなく解放するという点で、 Apple によってこの規則の例外であると文書化されていることが判明しました。

addObject:++objectAtIndex:配列の破壊の組み合わせがApple によって文書化されており、決して自動解放されないのか、それとも私がテストした例と Apple が含まれている例にたまたま含まれているのかについては、いくつかの議論があります。

サブクラスでまったく同じメモリ セマンティクスを持つメソッドを作成するにはどうすればよいですか?


最後の更新

NSMutableArray少し考えた結果、この場合は に比べて に基づく実装がより適切であると判断しましたNSPointerArrayretain新しいクラスには、以前の実装と同じ/autoreleaseペアがあることに注意してください。

Rob Napier のおかげで、私のobjectAtIndex:メソッドを変更しなくてもこの動作が変わることがわかりました。これは、このメソッドに関する最初の質問に答えています。

実用的なレベルでは、何人かの人々は、どの方法でも余分なretain/autoreleaseペアに理由なく取り組むことができると言いました。そうでないことを期待するのは合理的ではありません。したがって、それは私にとっていくつかのレベルで素晴らしい学習の機会でした.

コード ( に基づくNSMutableArray) は GitHub で入手できます: implementationheadertest (つまり-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 を囲むと@autoreleaseweakSingleton . このペアの正確なソースは明らかではありませんが、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ます。

4

2 に答える 2

1

これが「宿題によく似ている」と言った理由を詳しく説明します。これは私に多くの反対票を投じる可能性がありますが、後でこの質問を見つけた他の人にとっては良い学習事例としても役立ちます.

サブクラス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;
}

公正な警告: このコードのコンパイルや構文チェックは行っていません。おそらくいくつかのバグがありますが、通常は動作するはずです。add:さらに、この実装には、、、、、およびおそらく の実装がcountありませremoveObjectAtIndex:ん。ここで示しているのは、開始するためだけのものです。insertObject:atIndex:replaceObjectAtIndex:withObject:

于 2013-11-09T23:01:51.560 に答える