41

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

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

これらの2つの質問は、同様の根拠をカバーしています。

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

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

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

私はOSX10.7とiOS6.0用に開発しています。

4

8 に答える 8

24

弱参照をゼロにするには、OSX10.7またはiOS5が必要です。

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

おそらくブロックを悪用してこのような効果を達成できると言っています。

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

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

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

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

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

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

関連するオブジェクトに関する私の記事は次のとおりです。http://www.cocoanetics.com/2012/06/located-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];
}

自動解放プールブロック内に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 ();
}
于 2013-01-08T06:38:10.090 に答える
17

ゼロ化の弱い参照ラッパークラスのコードは次のとおりです。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
于 2013-01-08T16:28:42.640 に答える
8

NSMapTableあなたのために働くはずです。iOS6で利用できます。

于 2013-04-03T19:29:07.547 に答える
3
@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

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

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

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

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

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

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

于 2013-01-08T09:03:09.027 に答える
3

少なくともMacOSX10.5またはiOS6を使用している場合は、次のようにします。

  • NSPointerArray weakObjectsPointerArray / pointerArrayWithWeakObjectsは、NSArrayの弱参照代用です。
  • NSHashTable hashTableWithWeakObjects / weakObjectsHashTableは、NSSetの弱参照代用です。
  • NSMapTableは、NSDictionaryの弱参照代用です(弱いキーや弱い値を持つことができます)

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

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

于 2015-07-09T20:48:13.500 に答える
2

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

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

于 2013-01-09T10:56:12.787 に答える
2

次のコードを使用して、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:次に、コンテナクラスを呼び出す必要があります。

于 2013-12-24T12:11:45.137 に答える
0

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

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

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

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

于 2017-11-24T07:45:05.390 に答える