Ich bin etwas verwirrt über die Blockverwendung in Objective-C. Ich verwende derzeit ARC und habe eine ganze Reihe von Blöcken in meiner App, die sich derzeit immer auf self
anstatt auf dessen schwachen Verweis beziehen. Kann das die Ursache dafür sein, dass diese Blöcke self
behalten und die Zuordnung aufheben? Die Frage ist, sollte ich immer eine weak
Referenz von self
in einem Block verwenden?
-(void)handleNewerData:(NSArray *)arr
{
ProcessOperation *operation =
[[ProcessOperation alloc] initWithDataToProcess:arr
completion:^(NSMutableArray *rows) {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateFeed:arr rows:rows];
});
}];
[dataProcessQueue addOperation:operation];
}
ProcessOperation.h
@interface ProcessOperation : NSOperation
{
NSMutableArray *dataArr;
NSMutableArray *rowHeightsArr;
void (^callback)(NSMutableArray *rows);
}
ProcessOperation.m
-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{
if(self =[super init]){
dataArr = [NSMutableArray arrayWithArray:data];
rowHeightsArr = [NSMutableArray new];
callback = cb;
}
return self;
}
- (void)main {
@autoreleasepool {
...
callback(rowHeightsArr);
}
}
Es ist hilfreich, sich nicht auf den Teil strong
oder weak
der Diskussion zu konzentrieren. Konzentrieren Sie sich stattdessen auf den Teil des Zyklus .
Ein Beibehaltungszyklus ist eine Schleife, die auftritt, wenn Objekt A Objekt B, und Objekt B behält Objekt A bei. Wenn in dieser Situation eines der Objekte freigegeben wird:
Auf diese Weise bleiben diese beiden Objekte für die gesamte Lebensdauer des Programms im Speicher, obwohl sie freigegeben werden sollten, wenn alles ordnungsgemäß funktioniert.
Wir machen uns also Sorgen über die Beibehaltung von Zyklen , und es gibt nichts über Blöcke an und für sich, die diese Zyklen erzeugen. Dies ist kein Problem, zum Beispiel:
[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
[self doSomethingWithObject:obj];
}];
Der Block behält self
bei, aber self
behält den Block nicht bei. Wenn der eine oder andere freigegeben wird, wird kein Zyklus erstellt und alles wird wie gewünscht freigegeben.
Wo Sie in Schwierigkeiten geraten, ist etwas wie:
//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);
//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[self doSomethingWithObj:obj];
}];
Jetzt hat Ihr Objekt (self
) einen expliziten strong
Verweis auf den Block. Und der Block hat einen impliziten starken Verweis auf self
. Das ist ein Zyklus, und jetzt wird keines der Objekte ordnungsgemäß freigegeben.
Da in einer solchen Situation self
per Definition bereits einen strong
-Verweis auf den Block hat, ist dies normalerweise der Fall Dies lässt sich am einfachsten beheben, indem der zu verwendende Block explizit schwach auf self
verweist:
__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[weakSelf doSomethingWithObj:obj];
}];
Dies sollte jedoch nicht das Standardmuster sein, dem Sie folgen wenn Sie mit Blöcken arbeiten, die self
aufrufen! Dies sollte nur verwendet werden, um einen anderen Haltezyklus zwischen sich selbst und dem Block zu unterbrechen. Wenn Sie dieses Muster überall übernehmen, laufen Sie Gefahr, einen Block an etwas zu übergeben, das ausgeführt wurde, nachdem die Zuordnung von self
aufgehoben wurde.
//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
//By the time this gets called, "weakSelf" might be nil because it's not retained!
[weakSelf doSomething];
}];
Sie müssen nicht immer eine schwache Referenz verwenden. Wenn Ihr Block nicht beibehalten, sondern ausgeführt und dann verworfen wird, können Sie sich selbst stark erfassen, da dadurch kein Aufbewahrungszyklus erstellt wird. In einigen Fällen möchten Sie sogar, dass der Block das Selbst bis zur Fertigstellung des Blocks hält, damit die Zuordnung nicht vorzeitig aufgehoben wird. Wenn Sie den Block jedoch stark erfassen und sich selbst erfassen, wird ein Beibehaltungszyklus erstellt.
Ich stimme voll und ganz mit @jemmons überein.
"Dies sollte jedoch nicht das Standardmuster sein, dem Sie bei der Behandlung von Blöcken folgen, die self aufrufen. Dies sollte nur verwendet werden, um den sonst üblichen Beibehaltungszyklus zwischen self und dem Block zu unterbrechen. Wenn Sie dieses Muster überall übernehmen würden, würden Sie Es besteht die Gefahr, dass ein Block an etwas übergeben wird, das ausgeführt wurde, nachdem die Zuordnung zu "self" aufgehoben wurde. "
//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
//By the time this gets called, "weakSelf" might be nil because it's not retained!
[weakSelf doSomething];
}];
Um dieses Problem zu lösen, kann innerhalb des Blocks eine starke Referenz über das schwache Selbst definiert werden.
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
MyObject *strongSelf = weakSelf;
[strongSelf doSomething];
}];
Wie Leo betont, würde der Code, den Sie Ihrer Frage hinzugefügt haben, keinen starken Referenzzyklus vorschlagen (a.k.a., Beibehaltungszyklus). Ein betriebsbezogenes Problem, das einen starken Referenzzyklus verursachen könnte, ist, wenn der Betrieb nicht freigegeben wird. Ihr Code-Snippet weist zwar darauf hin, dass Sie Ihre Operation nicht als gleichzeitig definiert haben. Wenn dies jedoch der Fall ist, wird sie nicht freigegeben, wenn Sie nie isFinished
gepostet haben oder wenn Sie zirkuläre Abhängigkeiten hatten oder ähnliches. Und wenn die Operation nicht freigegeben wird, wird auch der Ansichtscontroller nicht freigegeben. Ich würde vorschlagen, einen Haltepunkt oder NSLog
in die dealloc
-Methode Ihrer Operation einzufügen und zu bestätigen, dass diese aufgerufen wird.
Du sagtest:
Ich verstehe den Begriff der Retain-Zyklen, bin mir aber nicht ganz sicher, was in Blöcken passiert, was mich ein wenig verwirrt
Die Probleme mit dem Aufbewahrungszyklus (starker Referenzzyklus), die bei Blöcken auftreten, sind genau wie die Probleme mit dem Aufbewahrungszyklus, die Sie kennen. Ein Block behält starke Verweise auf alle Objekte bei, die im Block erscheinen, und gibt diese starken Verweise erst frei, wenn der Block selbst freigegeben wird. Wenn also ein Block auf self
verweist oder nur auf eine Instanzvariable von self
verweist, die einen starken Bezug zu self beibehält, wird dieser erst aufgelöst, wenn der Block freigegeben wird (oder in diesem Fall bis die Unterklasse NSOperation
freigegeben wird.
Weitere Informationen finden Sie im Abschnitt Vermeiden starker Referenzzyklen beim Erfassen von Selbst des Abschnitts Programmieren mit Objective-C: Arbeiten mit Blöcken . dokumentieren.
Wenn Ihr View Controller immer noch nicht freigegeben wird, müssen Sie lediglich identifizieren, wo sich die nicht aufgelöste starke Referenz befindet (vorausgesetzt, Sie haben bestätigt, dass die Zuordnung von NSOperation
aufgehoben wird). Ein häufiges Beispiel ist die Verwendung eines sich wiederholenden NSTimer
. Oder ein benutzerdefiniertes delegate
oder ein anderes Objekt, das fälschlicherweise eine strong
Referenz verwaltet. Sie können häufig Instrumente verwenden, um herauszufinden, wo Objekte ihre starken Referenzen erhalten, z.
Oder in Xcode 5:
Einige Erklärungen ignorieren eine Bedingung bezüglich des Aufbewahrungszyklus. [Wenn eine Gruppe von Objekten durch einen Kreis starker Beziehungen verbunden ist, halten sie sich gegenseitig am Leben, auch wenn es keine starken Referenzen von außerhalb der Gruppe gibt.] Weitere Informationen finden Sie unter - Dokument