webentwicklung-frage-antwort-db.com.de

NSArray von schwachen Referenzen (__unsafe_unretained) auf Objekte unter ARC

Ich muss schwache Referenzen auf Objekte in einem NSArray speichern, um Beibehaltungszyklen zu verhindern. Ich bin mir nicht sicher, welche Syntax ich verwenden soll. Ist das der richtige Weg?

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];

Beachten Sie, dass ich iOS 4.x unterstützen muss, also den __unsafe_unretained Anstelle von __weak.


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

Wenn Sie echte __weak - Zeiger verwenden möchten (nicht __unsafe_unretained), Lesen Sie stattdessen diese Frage: Auflistung von schwachen Referenzen unter ARC

68
Emile Cormier

Wie Jason sagte, können Sie NSArray nicht dazu bringen, schwache Referenzen zu speichern. Der einfachste Weg, Emiles Vorschlag, ein Objekt in ein anderes Objekt zu packen, in dem ein schwacher Verweis darauf gespeichert ist, umzusetzen, ist der folgende:

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

Eine weitere Option: ein Kategorie , das NSMutableArray zum optionalen Speichern von schwachen Referenzen veranlasst.

Beachten Sie, dass dies "unsichere, nicht gespeicherte" Verweise sind und keine schwachen Verweise, die sich selbst auf Null setzen. Wenn das Array noch vorhanden ist, nachdem die Zuordnung der Objekte aufgehoben wurde, verfügen Sie über eine Reihe von Junk-Zeigern.

73
yuji

Die Lösungen zur Verwendung eines NSValue - Helfers oder zum Erstellen eines Auflistungsobjekts (Array, Set, Dict) und Deaktivieren der Retain/Release-Rückrufe sind beide keine 100% ausfallsicheren Lösungen in Bezug auf die Verwendung von ARC.

Wie aus verschiedenen Kommentaren zu diesen Vorschlägen hervorgeht, funktionieren solche Objektreferenzen nicht wie echte schwache Referenzen:

Eine von ARC unterstützte "richtige" schwache Eigenschaft weist zwei Verhaltensweisen auf:

  1. Hält keinen starken Verweis auf das Zielobjekt. Das bedeutet, dass das Objekt freigegeben wird, wenn auf das Objekt keine starken Verweise verweisen.
  2. Wenn das ref'd-Objekt freigegeben wird, wird die schwache Referenz zu null.

Die obigen Lösungen entsprechen zwar dem Verhalten Nr. 1, weisen jedoch nicht das Verhalten Nr. 2 auf.

Um auch Verhalten 2 zu erhalten, müssen Sie Ihre eigene Helferklasse deklarieren. Es hat nur eine schwache Eigenschaft, um Ihre Referenz zu halten. Anschließend fügen Sie dieses Hilfsobjekt der Auflistung hinzu.

Und noch eins: iOS6 und OSX 10.8 bieten angeblich eine bessere Lösung:

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

Diese sollten Ihnen Container liefern, die schwache Referenzen enthalten (beachten Sie jedoch die folgenden Kommentare von Matt).

55

Ich bin neu in Objective-C, nachdem ich 20 Jahre lang C++ geschrieben habe.

Meiner Ansicht nach eignet sich Objective-C hervorragend für lose verknüpfte Nachrichtenübermittlung, ist jedoch für die Datenverwaltung schrecklich.

Stellen Sie sich vor, wie glücklich ich war zu entdecken, dass xcode 4.3 Objective-C++ unterstützt!

Daher benenne ich jetzt alle meine .m-Dateien in .mm um (wird als Objective-C++ kompiliert) und verwende C++ - Standardcontainer für die Datenverwaltung.

Somit wird das Problem "Array von schwachen Zeigern" zu einem Standardvektor von __schwachen Objektzeigern:

#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
  }
}

Welches ist gleichbedeutend mit dem C++ - Idiom:

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
  }
}

Proof of Concept (im Lichte von Tommys Bedenken hinsichtlich der Vektorumverteilung):

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]);
}

beispiel für eine Protokollausgabe:

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

Wenn Sie keine bestimmte Bestellung benötigen, können Sie NSMapTable mit speziellen Schlüssel-/Wertoptionen verwenden

NSPointerFunctionsWeakMemory

Verwendet schwache Lese- und Schreibbarrieren, die für ARC oder GC geeignet sind. Wenn Sie NSPointerFunctionsWeakMemory-Objektreferenzen verwenden, werden diese in der letzten Version auf NULL gesetzt.

13
Erik Aigner

Ich glaube, die beste Lösung dafür ist die Verwendung von NSHashTable oder NSMapTable. Der Schlüssel oder/und der Wert können schwach sein. Sie können hier mehr darüber lesen: http://nshipster.com/nshashtable-and-nsmaptable/

10
Yaniv De Ridder

Um einen schwachen Selbstverweis auf NSMutableArray hinzuzufügen, erstellen Sie eine benutzerdefinierte Klasse mit einer schwachen Eigenschaft (siehe unten).

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];

}

Schritt 3: Später, wenn die Eigenschaft delegateWeakReference == nil, das Objekt kann aus dem Array entfernt werden

Die Eigenschaft hat den Wert Null, und die Verweise werden unabhängig von diesen Array-Verweisen zu gegebener Zeit freigegeben

4

Die einfachste Lösung:

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);

Hinweis: Und das funktioniert auch unter iOS 4.x.

4
ArtFeel

Nein, das stimmt nicht. Das sind eigentlich keine schwachen Referenzen. Sie können derzeit keine schwachen Referenzen in einem Array speichern. Sie müssen ein veränderbares Array haben und die Verweise entfernen, wenn Sie damit fertig sind, oder das gesamte Array entfernen, wenn Sie damit fertig sind, oder eine eigene Datenstruktur erstellen, die dies unterstützt.

Hoffentlich wird dies in naher Zukunft behoben (eine schwache Version von NSArray).

3
Jason Coco

Ich bin gerade mit demselben Problem konfrontiert und habe festgestellt, dass meine Vor-ARC-Lösung nach der Konvertierung mit ARC wie vorgesehen funktioniert.

// 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

Ausgabe:

2013-06-30 00: 59: 10.266 not_retained_collection_test [28997: 907] built 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] konstruiert 2013-06-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] konstruiert 2013-06-30 00: 59: 10.882 not_retained_collection_test [28997: 907] addet to set 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] deallocated 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] deallocated 2013-06-30 00:59 : 10.884 not_retained_collection_test [28997: 907] freigegeben 2013-06-30 00: 59: 10.885 not_retained_collection_test [28997: 907] * - [TestObj responsToSelector:]: Nachricht an freigegebene Instanz 0x1f03c8c0 gesendet

Überprüft mit iOS-Versionen 4.3, 5.1, 6.2. Hoffe, es wird jemandem nützlich sein.

2
eug

Wenn Sie schwache Referenzen auf Null setzen müssen, lesen Sie diese Antwort für Code, den Sie für eine Wrapper-Klasse verwenden können.

Andere Antworten auf diese Frage schlagen einen blockbasierten Wrapper vor und Möglichkeiten, um automatisch nullte Elemente aus der Auflistung zu entfernen.

1
paulmelnikow

Wenn Sie dieses Verhalten häufig verwenden, wird es Ihrer eigenen NSMutableArray-Klasse (Unterklasse von NSMutableArray) angezeigt, wodurch die Anzahl der Aufbewahrungen nicht erhöht wird.

Sie sollten so etwas haben:

-(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