web-dev-qa-db-ja.com

ARCの下のオブジェクトへの弱い参照(__unsafe_unretained)のNSArray

保持サイクルを防ぐために、NSArrayにオブジェクトへの弱い参照を保存する必要があります。使用する適切な構文がわからない。これは正しい方法ですか?

Foo* foo1 = [[Foo alloc] init];
Foo* foo2 = [[Foo alloc] init];

__unsafe_unretained Foo* weakFoo1 = foo1;
__unsafe_unretained Foo* weakFoo2 = foo2;

NSArray* someArray = [NSArray arrayWithObjects:weakFoo1, weakFoo2, nil];

iOS 4.xをサポートする必要があることに注意してください。したがって、__unsafe_unretainedの代わりに__weakがサポートされます。


[〜#〜] edit [〜#〜](2015-02-18):

__weakではなく)真の__unsafe_unretainedポインターを使用したい場合は、代わりにこの質問を確認してください: ARCの下の弱参照のゼロ化のコレクション

68
Emile Cormier

ジェイソンが言ったように、NSArrayに弱い参照を保存させることはできません。オブジェクトへの弱い参照を格納する別のオブジェクト内にオブジェクトをラップするというEmileの提案を実装する最も簡単な方法は次のとおりです。

NSValue *value = [NSValue valueWithNonretainedObject:myObj];
[array addObject:value];

別のオプション:NSMutableArrayにオプションで弱い参照を格納させる category

これらは「安全でない保持されていない」参照であり、自己ゼロ化の弱い参照ではないことに注意してください。オブジェクトの割り当てが解除された後も配列が残っている場合は、大量のジャンクポインターがあります。

73
yuji

NSValueヘルパーを使用するか、コレクション(配列、設定、辞書)オブジェクトを作成し、その保持/ を無効にするソリューションリリースコールバック は、ARCの使用に関して100%フェイルセーフソリューションではありません。

これらの提案に対するさまざまなコメントが指摘しているように、そのようなオブジェクト参照は真の弱い参照のようには機能しません。

ARCでサポートされている「適切な」弱いプロパティには、2つの動作があります。

  1. ターゲットオブジェクトへの強い参照を保持しません。つまり、オブジェクトを指す強力な参照がオブジェクトにない場合、オブジェクトの割り当てが解除されます。
  2. Ref'dオブジェクトの割り当てが解除されると、弱参照はゼロになります。

現在、上記のソリューションは動作#1に準拠していますが、それらは#2を示していません。

動作#2も取得するには、独自のヘルパークラスを宣言する必要があります。参照を保持するための弱いプロパティが1つだけあります。次に、このヘルパーオブジェクトをコレクションに追加します。

ああ、もう1つ:iOS6とOSX 10.8がより良いソリューションを提供すると思われます。

[NSHashTable weakObjectsHashTable]
[NSPointerArray weakObjectsPointerArray]
[NSPointerArray pointerArrayWithOptions:]

これらは、弱い参照を保持するコンテナを提供するはずです(ただし、以下のmattのコメントに注意してください)。

55

私は、C++を20年間書いた後、objective-Cを初めて使いました。

私の見解では、objective-Cは疎結合メッセージングに優れていますが、データ管理には恐ろしいです。

Xcode 4.3がObjective-C++をサポートしていることを発見できて、どれだけ幸せか想像してみてください!

そこで、すべての.mファイルの名前を.mm(objective-c ++としてコンパイル)に変更し、データ管理にc ++標準コンテナーを使用します。

したがって、「弱いポインターの配列」問題は、__ weakオブジェクトポインターのstd :: vectorになります。

#include <vector>

@interface Thing : NSObject
@end

// declare my vector
std::vector<__weak Thing*> myThings;

// store a weak reference in it
Thing* t = [Thing new];
myThings.Push_back(t);

// ... some time later ...

for(auto weak : myThings) {
  Thing* strong = weak; // safely lock the weak pointer
  if (strong) {
    // use the locked pointer
  }
}

これは、c ++イディオムと同等です。

std::vector< std::weak_ptr<CppThing> > myCppThings;
std::shared_ptr<CppThing> p = std::make_shared<CppThing>();
myCppThings.Push_back(p);

// ... some time later ...

for(auto weak : myCppThings) {
  auto strong = weak.lock(); // safety is enforced in c++, you can't dereference a weak_ptr
  if (strong) {
    // use the locked pointer
  }
}

概念実証(ベクトルの再割り当てに関するトミーの懸念に照らして):

main.mm:

#include <vector>
#import <Foundation/Foundation.h>

@interface Thing : NSObject
@end

@implementation Thing


@end

extern void foo(Thing*);

int main()
{
    // declare my vector
    std::vector<__weak Thing*> myThings;

    // store a weak reference in it while causing reallocations
    Thing* t = [[Thing alloc]init];
    for (int i = 0 ; i < 100000 ; ++i) {
        myThings.Push_back(t);
    }
    // ... some time later ...

    foo(myThings[5000]);

    t = nullptr;

    foo(myThings[5000]);
}

void foo(Thing*p)
{
    NSLog(@"%@", [p className]);
}

ログ出力の例:

2016-09-21 18:11:13.150 foo2[42745:5048189] Thing
2016-09-21 18:11:13.152 foo2[42745:5048189] (null)
25
Richard Hodges

特定の順序を必要としない場合は、特別なキー/値オプションでNSMapTableを使用できます

NSPointerFunctionsWeakMemory

ARCまたはGCに適した弱い読み取りおよび書き込みバリアを使用します。 NSPointerFunctionsWeakMemoryオブジェクト参照を使用すると、前回のリリースでNULLになります。

13
Erik Aigner

これに対する最善の解決策は、NSHashTableまたはNSMapTableを使用することだと思います。キーまたは値が弱い場合があります。詳しくはこちらをご覧ください: http://nshipster.com/nshashtable-and-nsmaptable/

10
Yaniv De Ridder

NSMutableArrayに弱い自己参照を追加するには、以下に示すように、弱いプロパティを持つカスタムクラスを作成します。

NSMutableArray *array = [NSMutableArray new];

Step 1: create a custom class 

@interface DelegateRef : NSObject

@property(nonatomic, weak)id delegateWeakReference;

@end

Step 2: create a method to add self as weak reference to NSMutableArray. But here we add the DelegateRef object

-(void)addWeakRef:(id)ref
{

  DelegateRef *delRef = [DelegateRef new];

  [delRef setDelegateWeakReference:ref] 

  [array addObject:delRef];

}

ステップ3:後で、プロパティdelegateWeakReference == nil、オブジェクトは配列から削除できます

プロパティはnilになり、参照はこの配列参照とは無関係に適切な時間に割り当て解除されます

4

最も簡単な解決策:

NSMutableArray *array = (__bridge_transfer NSMutableArray *)CFArrayCreateMutable(nil, 0, nil);
NSMutableDictionary *dictionary = (__bridge_transfer NSMutableDictionary *)CFDictionaryCreateMutable(nil, 0, nil, nil);
NSMutableSet *set = (__bridge_transfer NSMutableSet *)CFSetCreateMutable(nil, 0, nil);

注:これはiOS 4.xでも機能します。

4
ArtFeel

いいえ、それは正しくありません。これらは実際には弱い参照ではありません。現在、配列に弱参照を実際に格納することはできません。可変配列を持ち、参照が終わったら参照を削除するか、配列が完了したら配列全体を削除するか、それをサポートする独自のデータ構造をロールする必要があります。

うまくいけば、これは近い将来に対処するものです(NSArrayの弱いバージョン)。

3
Jason Coco

私はちょうど同じ問題に直面し、設計前のARCで変換した後、ARC以前のソリューションが機能することがわかりました。

// function allocates mutable set which doesn't retain references.
NSMutableSet* AllocNotRetainedMutableSet() {
    CFMutableSetRef setRef = NULL;
    CFSetCallBacks notRetainedCallbacks = kCFTypeSetCallBacks;
    notRetainedCallbacks.retain = NULL;
    notRetainedCallbacks.release = NULL;
    setRef = CFSetCreateMutable(kCFAllocatorDefault,
    0,
    &notRetainedCallbacks);
    return (__bridge NSMutableSet *)setRef;
}

// test object for debug deallocation
@interface TestObj : NSObject
@end
@implementation TestObj
- (id)init {
   self = [super init];
   NSLog(@"%@ constructed", self);
   return self;
}
- (void)dealloc {
   NSLog(@"%@ deallocated", self);
}
@end


@interface MainViewController () {
   NSMutableSet *weakedSet;
   NSMutableSet *usualSet;
}
@end

@implementation MainViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
      weakedSet = AllocNotRetainedMutableSet();
      usualSet = [NSMutableSet new];
   }
    return self;
}

- (IBAction)addObject:(id)sender {
   TestObj *obj = [TestObj new];
   [weakedSet addObject:obj]; // store unsafe unretained ref
   [usualSet addObject:obj]; // store strong ref
   NSLog(@"%@ addet to set", obj);
   obj = nil;
   if ([usualSet count] == 3) {
      [usualSet removeAllObjects];  // deallocate all objects and get old fashioned crash, as it was required.
      [weakedSet enumerateObjectsUsingBlock:^(TestObj *invalidObj, BOOL *stop) {
         NSLog(@"%@ must crash here", invalidObj);
      }];
   }
}
@end

出力:

2013-06-30 00:59:10.266 not_retained_collection_test [28997:907]構築済み2013-06-30 00:59:10.267 not_retained_collection_test [28997:907] addet to set 2013-06-30 00:59:10.581 not_retained_collection_test [28997: 907] 2013年6月30日00:59:10.582構築済みnot_retained_collection_test [28997:907] addet to set 2013-06-30 00:59:10.881 not_retained_collection_test [28997:907]構築済み2013-06-30 00:59:10.882 not_retained_collection_test [28997:907]セット2013-06-30 00:59:10.883 not_retained_collection_test [28997:907]割り当て解除2013-06-30 00:59:10.883 not_retained_collection_test [28997:907]割り当て解除2013-06-30 00:59 :10.884 not_retained_collection_test [28997:907] deallocated 2013-06-30 00:59:10.885 not_retained_collection_test [28997:907] *-[TestObj respondsToSelector:]:割り当て解除されたインスタンス0x1f03c8c0に送信されたメッセージ

IOSバージョン4.3、5.1、6.2で確認済み。それが誰かに役立つことを願っています。

2
eug

zeroing弱参照が必要な場合は、ラッパークラスに使用できるコードについて this answer を参照してください。

その質問 に対する他の回答は、ブロックベースのラッパー、およびゼロ化された要素をコレクションから自動的に削除する方法を示唆しています。

1
paulmelnikow

このコンポーネントを頻繁に使用する場合は、保持カウントを増加させない独自のNSMutableArrayクラス(NSMutableArrayのサブクラス)に示されます。

次のようなものが必要です。

-(void)addObject:(NSObject *)object {
    [self.collection addObject:[NSValue valueWithNonretainedObject:object]];
}

-(NSObject*) getObject:(NSUInteger)index {

    NSValue *value = [self.collection objectAtIndex:index];
    if (value.nonretainedObjectValue != nil) {
        return value.nonretainedObjectValue;
    }

    //it's Nice to clean the array if the referenced object was deallocated
    [self.collection removeObjectAtIndex:index];

    return nil;
}
1
Ciprian C