web-dev-qa-db-ja.com

ARCでのゼロ参照の弱参照のコレクション

ARCでゼロ化弱参照の配列を取得するにはどうすればよいですか?配列にオブジェクトを保持させたくありません。そして、配列の要素が割り当て解除されたときにそれらを削除するか、それらのエントリをnilに設定したいと思います。

同様に、どうすれば辞書でそれを行うことができますか?辞書に値を保持させたくない。繰り返しになりますが、値が解放されたときにディクショナリ要素を削除するか、値をnilに設定します。 (少なくとも対応する値が割り当て解除されるまで、一意の識別子であるキーを保持する必要があります。)

これらの2つの質問は同様の理由をカバーしています:

しかし、どちらもゼロ参照を求めていません。

ドキュメントによると、NSPointerArrayもNSHashMapも、ARCでの弱い参照をサポートしていません。 NSValueはゼロでないため、NSValueのnonretainedObjectValueも機能しません。

私が目にする唯一の解決策は、 この回答が最後に述べているように、(weak)プロパティを使用してmyNSValueのようなラッパークラスを作成することです 。私が見ないより良い方法はありますか?

私はOS X 10.7とiOS 6.0用に開発しています。

40
paulmelnikow

ゼロ参照の弱参照ラッパークラスのコードを次に示します。 NSArray、NSSet、NSDictionaryで正しく動作します。

このソリューションの利点は、古いOSと互換性があり、それがシンプルであることです。欠点は、反復するときに、使用する前に-nonretainedObjectValueがnil以外であることを確認する必要がある可能性が高いことです。

これは、Cocoaneticsの回答の最初の部分のラッパーと同じアイデアで、ブロックを使用して同じことを実行します。

WeakReference.h

@interface WeakReference : NSObject {
    __weak id nonretainedObjectValue;
    __unsafe_unretained id originalObjectValue;
}

+ (WeakReference *) weakReferenceWithObject:(id) object;

- (id) nonretainedObjectValue;
- (void *) originalObjectValue;

@end

WeakReference.m

@implementation WeakReference

- (id) initWithObject:(id) object {
    if (self = [super init]) {
        nonretainedObjectValue = originalObjectValue = object;
    }
    return self;
}

+ (WeakReference *) weakReferenceWithObject:(id) object {
    return [[self alloc] initWithObject:object];
}

- (id) nonretainedObjectValue { return nonretainedObjectValue; }
- (void *) originalObjectValue { return (__bridge void *) originalObjectValue; }

// To work appropriately with NSSet
- (BOOL) isEqual:(WeakReference *) object {
    if (![object isKindOfClass:[WeakReference class]]) return NO;
    return object.originalObjectValue == self.originalObjectValue;
}

@end
17
paulmelnikow

弱参照のゼロ化にはOS X 10.7またはiOS 5が必要です。

コード、ivar、またはブロックでのみ弱い変数を定義できます。私の知る限り、ARCはコンパイル時に有効になるため、弱い変数を動的に(実行時に)作成する方法はありません。コードを実行すると、保持とリリースがすでに追加されています。

あなたはおそらくこのような効果を達成するためにブロックを乱用することができると言いました。

単に参照を返すブロックがあります。

__weak id weakref = strongref;
[weakrefArray addObject:[^{ return weakref; } copy]];

ヒープにコピーするには、ブロックをコピーする必要があることに注意してください。

これで、いつでも配列をウォークできます。ブロック内の割り当て解除されたオブジェクトはnilを返します。その後、それらを削除できます。

弱い参照がゼロになると、コードを自動的に実行することはできません。これが必要な場合は、関連付けられたオブジェクトの機能を利用できます。それらは、関連付けられているオブジェクトと同時に割り当て解除されます。したがって、オブジェクトの終了について弱いコレクションに通知する独自の監視タグを作成できます。

(関連付けが唯一の参照である場合)deallocを監視する1つの関連付けられたオブジェクトがあり、関連付けられたオブジェクトは監視するコレクションへのポインターを持ちます。次に、歩哨のdeallocで、weakコレクションを呼び出して、監視対象のオブジェクトがなくなったことを通知します。

これが関連オブジェクトに関する私の記事です: http://www.cocoanetics.com/2012/06/associated-objects/

これが私の実装です:

---- DTWeakCollection.h

@interface DTWeakCollection : NSObject

- (void)checkInObject:(id)object;

- (NSSet *)allObjects;

@end

---- DTWeakCollection.m

#import "DTWeakCollection.h"
#import "DTWeakCollectionSentry.h"
#import <objc/runtime.h>

static char DTWeakCollectionSentryKey;

@implementation DTWeakCollection
{
    NSMutableSet *_entries;
}

- (id)init
{
    self = [super init];
    if (self)
    {
        _entries = [NSMutableSet set];
    }
    return self;
}

- (void)checkInObject:(id)object
{
    NSUInteger hash = (NSUInteger)object;

    // make weak reference
    NSNumber *value = [NSNumber numberWithUnsignedInteger:hash];
    [_entries addObject:value];

    // make sentry
    DTWeakCollectionSentry *sentry = [[DTWeakCollectionSentry alloc] initWithWeakCollection:self forObjectWithHash:hash];
    objc_setAssociatedObject(object, &DTWeakCollectionSentryKey, sentry, OBJC_ASSOCIATION_RETAIN);
}

- (void)checkOutObjectWithHash:(NSUInteger)hash
{
    NSNumber *value = [NSNumber numberWithUnsignedInteger:hash];
    [_entries removeObject:value];
}

- (NSSet *)allObjects
{
    NSMutableSet *tmpSet = [NSMutableSet set];

    for (NSNumber *oneHash in _entries)
    {
        // hash is actually a pointer to the object
        id object = (__bridge id)(void *)[oneHash unsignedIntegerValue];
        [tmpSet addObject:object];
    }

    return [tmpSet copy];
}

@end

---- DTWeakCollectionSentry.h

#import <Foundation/Foundation.h>
@class DTWeakCollection;

@interface DTWeakCollectionSentry : NSObject

- (id)initWithWeakCollection:(DTWeakCollection *)weakCollection forObjectWithHash:(NSUInteger)hash;

@end

--- DTWeakCollectionSentry.m


#import "DTWeakCollectionSentry.h"
#import "DTWeakCollection.h"

@interface DTWeakCollection (private)

- (void)checkOutObjectWithHash:(NSUInteger)hash;

@end

@implementation DTWeakCollectionSentry
{
    __weak DTWeakCollection *_weakCollection;
    NSUInteger _hash;
}

- (id)initWithWeakCollection:(DTWeakCollection *)weakCollection forObjectWithHash:(NSUInteger)hash
{
    self = [super init];

    if (self)
    {
        _weakCollection = weakCollection;
        _hash = hash;
    }

    return self;
}

- (void)dealloc
{
    [_weakCollection checkOutObjectWithHash:_hash];
}

@end

これは次のように使用されます。

NSString *string = @"bla";

@autoreleasepool {
_weakCollection = [[DTWeakCollection alloc] init];
    [_weakCollection checkInObject:string];

__object = [NSNumber numberWithInteger:1123333];

[_weakCollection checkInObject:__object];
}

autoreleaseプールブロック内でallObjectsを出力する場合、そこには2つのオブジェクトがあります。外にはストリングしかありません。

エントリのdeallocでオブジェクト参照がすでにnilであるため、__ weakを使用できないことがわかりました。代わりに、オブジェクトのメモリアドレスをハッシュとして使用しています。これらはまだ_entriesにありますが、実際のオブジェクトとして扱うことができ、allObjectsは強力な参照の自動解放された配列を返します。

注:これはスレッドセーフではありません。内部_entriesセットへのアクセスと変更を同期するように注意する必要がある非メインキュー/スレッドのdeallocを処理します。

注2:2回目のチェックインで関連する歩哨が上書きされるため、これは現在、単一のウィークコレクションにチェックインするオブジェクトでのみ機能します。複数の弱いコレクションでこれが必要な場合は、代わりに歩哨にそれらのコレクションの配列が必要です。

注3:保持サイクルを回避するために、歩哨のコレクションへの参照を弱いものに変更しました。

注4:ブロック構文を処理するtypedef関数とヘルパー関数を次に示します。

typedef id (^WeakReference)(void);

WeakReference MakeWeakReference (id object) {
    __weak id weakref = object;
    return [^{ return weakref; } copy];
}

id WeakReferenceNonretainedObjectValue (WeakReference ref) {
    if (ref == nil)
        return nil;
    else
        return ref ();
}
24
Cocoanetics

NSMapTableでうまくいくはずです。 iOS 6で利用可能です。

8
Tricertops
@interface Car : NSObject
@end
@implementation Car
-(void) dealloc {
    NSLog(@"deallocing");
}
@end


int main(int argc, char *argv[])
{
    @autoreleasepool {
        Car *car = [Car new];

        NSUInteger capacity = 10;
        id __weak *_objs = (id __weak *)calloc(capacity,sizeof(*_objs));
        _objs[0] = car;
        car = nil;

        NSLog(@"%p",_objs[0]);
        return EXIT_SUCCESS;
    }
}

出力:

2013-01-08 10:00:19.171 X[6515:c07] deallocing
2013-01-08 10:00:19.172 X[6515:c07] 0x0

edit:このアイデアに基づいて、最初から サンプルの弱いマップコレクション を作成しました。それは機能しますが、いくつかの理由で醜いです:

NSObjectのカテゴリ を使用して、キーの@properties、次のマップバケット、およびオブジェクトを所有するコレクションへの参照を追加しました。

オブジェクトをnilすると、コレクションから消えます。

ただし、マップに動的な容量を持たせるには、要素の更新数を受け取って負荷係数を計算し、必要に応じて容量を拡張する必要があります。つまり、要素を追加するたびに配列全体を反復処理するΘ(n)更新を実行する場合を除きます。コレクションに追加するサンプルオブジェクトのdeallocメソッドのコールバックでこれを行いました。元のオブジェクトを編集したり(簡潔にするために行いました)、スーパークラスから継承したり、deallocをスウィズルしたりできます。いずれにせよ、醜い。

ただし、固定容量のコレクションが必要ない場合は、コールバックは必要ありません。コレクションは separate chaining を使用し、ハッシュ関数の均一な分布を仮定すると、パフォーマンスはΘ(1 + n/m)で、n = elements、m = capacityになります。しかし(より多くの)連鎖を壊さないようにするには、前のリンクをカテゴリ@propertyとして追加し、それを要素の割り当て解除の次の要素にリンクする必要があります。そして、deallocに触れたら、要素が削除されていることをコレクションに通知するのも同じです(これが現在行われていることです)。

最後に、プロジェクトのテストは最小限であり、見落としがあった可能性があることに注意してください。

3
Jano

MacOS X 10.5またはiOS6以降を使用している場合は、次のようにします。

  • NSPointerArray weakObjectsPointerArray/pointerArrayWithWeakObjectsは、NSArrayの弱参照スタンドインです
  • NSHashTable hashTableWithWeakObjects/weakObjectsHashTableは、NSSetの弱参照スタンドインです
  • NSMapTableは、NSDictionaryの弱い参照スタンドインです(弱いキーや弱い値を持つ可能性があります)。

コレクションはオブジェクトが削除されたことにすぐには気付かない場合があるため、カウントはさらに高くなる可能性があり、関連付けられたオブジェクトが削除された場合でもキーが存在する可能性があります。NSPointerArrayには、理論的にはすべてを削除する必要がある-compactメソッドがあります。 nilledポインタ。 NSMapTableのドキュメントでは、weakToStrongマップのキーはテーブルのサイズが変更されるまで(事実上nilであっても)テーブルに残ります。つまり、論理的に参照されなくなっても、強力なオブジェクトポインタはメモリに残る可能性があります。

編集:ARCについて質問された元のポスターが表示されます。これらのコンテナーをARCで使用できるようになる前は、確かに10.8とiOS 6だったと思います。以前の「弱い」ものはGC用でした。 ARCは10.7までサポートされていなかったため、10.6ではなくそのリリースをサポートする必要があるかどうかは本当に問題です。この場合、独自にロールする必要があります(またはNSPointerFunctionsでカスタム関数を使用し、次にそれを使用できます) NSPointerArray、NSHashTable、およびNSMapTable)。

3
Carl Lindberg

NSMutableDictionaryとNSMutableSetのスレッドセーフでない弱参照バージョンを作成するだけです。ここにコード: https://Gist.github.com/449228

NSMutableArrayの場合、nilを含めることができず、オブジェクトが配列に複数回追加される可能性があるため、状況はより複雑になります。しかし、それを実装することは可能です。

2
Bryan Chen

次のコードを使用して、NSMutableSetのカテゴリを追加するだけです。

@interface WeakReferenceObj : NSObject
@property (nonatomic, weak) id weakRef;
@end

@implementation WeakReferenceObj
+ (id)weakReferenceWithObj:(id)obj{
    WeakReferenceObj *weakObj = [[WeakReferenceObj alloc] init];
    weakObj.weakRef = obj;
    return weakObj;
}
@end

@implementation NSMutableSet(WeakReferenceObj)
- (void)removeDeallocRef{
    NSMutableSet *deallocSet = nil;
    for (WeakReferenceObj *weakRefObj in self) {
        if (!weakRefObj.weakRef) {
            if (!deallocSet) {
                deallocSet = [NSMutableSet set];
            }
            [deallocSet addObject:weakRefObj];
        }
    }
    if (deallocSet) {
        [self minusSet:deallocSet];
    }
}

- (void)addWeakReference:(id)obj{
    [self removeDeallocRef];
    [self addObject:[WeakReferenceObj weakReferenceWithObj:obj]];
}
@end

NSMutableArrayおよびNSMutableDictionaryのカテゴリを作成する同じ方法。

DidReceiveMemoryWarningのdealloc参照を削除する方が良いでしょう。

- (void)didReceiveMemoryWarning{
    [yourWeakReferenceSet removeDeallocRef];
}

次に、コンテナクラスに対してaddWeakReference:を呼び出す必要があります。

2
simalone

この問題の完全な解決策については、BMCommonsフレームワークの一部である BMNullableArray クラスを参照してください。

このクラスを使用すると、nilオブジェクトを挿入でき、そこに含まれるオブジェクトを弱く参照するオプションがあります(割り当てが解除されたときに自動的にnillします)。

自動削除(実装しようとしました)の問題は、スレッドの安全性の問題が発生することです。これは、オブジェクトが割り当て解除される時点が保証されていないためです。これは、配列の反復中にも発生する可能性があります。

このクラスは、NSPointerArrayを改良したものです。これは、下位レベルの詳細を抽象化し、ポインタの代わりにオブジェクトを操作できるようにするためです。 NSFastEnumerationもサポートされ、そこにnil参照がある配列を反復処理します。

0