Zyklus mit Blöcken auf "Selbst" halten

167

Ich fürchte, diese Frage ist ziemlich einfach, aber ich denke, sie ist für viele Objective-C-Programmierer relevant, die in Blöcke geraten.

Was ich gehört habe ist, dass, da Blöcke lokale Variablen erfassen, auf die in ihnen als constKopien verwiesen wird , die Verwendung selfinnerhalb eines Blocks zu einem Aufbewahrungszyklus führen kann, sollte dieser Block kopiert werden. Wir sollten also __blockden Block zwingen, direkt damit umzugehen, selfanstatt ihn kopieren zu lassen.

__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];

statt nur

[someObject messageWithBlock:^{ [self doSomething]; }];

Was ich gerne wissen würde, ist Folgendes: Wenn dies zutrifft, gibt es eine Möglichkeit, die Hässlichkeit zu vermeiden (abgesehen von der Verwendung von GC)?

Jonathan Sterling
quelle
3
Ich rufe gerne meine selfProxies an, thisnur um die Dinge umzudrehen. In JavaScript nenne ich meine thisVerschlüsse self, damit es sich gut und ausgewogen anfühlt. :)
devios1
Ich frage mich, ob es eine gleichwertige Aktion gibt, wenn ich Swift-Blöcke verwende
Ben Lu
@ BenLu absolut! in Swift-Verschlüssen (und Funktionen, die herumgereicht werden und die implizit oder explizit Selbst erwähnen) behalten sich selbst bei. Manchmal ist dies erwünscht, und manchmal wird ein Zyklus erstellt (weil der Verschluss selbst dem Selbst gehört (oder etwas gehört, das ihm gehört). Der Hauptgrund dafür ist ARC.
Ethan
1
Um Probleme zu vermeiden, ist die geeignete Methode zur Definition von 'self', die in einem Block verwendet werden soll, '__typeof (self) __weak schwachSelf = self;' um eine schwache Referenz zu haben.
XLE_22

Antworten:

169

Genau genommen hat die Tatsache, dass es sich um eine konstante Kopie handelt, nichts mit diesem Problem zu tun. Blöcke behalten alle obj-c-Werte bei, die beim Erstellen erfasst werden. Es kommt einfach so vor, dass die Problemumgehung für das Problem mit der Konstantenkopie mit der Problemumgehung für das Problem mit dem Beibehalten identisch ist. nämlich unter Verwendung der __blockSpeicherklasse für die Variable.

Um Ihre Frage zu beantworten, gibt es hier auf keinen Fall eine echte Alternative. Wenn Sie Ihre eigene blockbasierte API entwerfen und dies sinnvoll ist, wird dem Block möglicherweise der Wert von selfin als Argument übergeben. Leider ist dies für die meisten APIs nicht sinnvoll.

Bitte beachten Sie, dass das Referenzieren eines Ivar genau das gleiche Problem hat. Wenn Sie auf einen Ivar in Ihrem Block verweisen müssen, verwenden Sie stattdessen entweder eine Eigenschaft oder verwenden Sie bself->ivar.


Nachtrag: Beim Kompilieren als ARC werden __blockkeine Haltezyklen mehr unterbrochen. Wenn Sie für ARC kompilieren, müssen Sie __weakoder __unsafe_unretainedverwenden.

Lily Ballard
quelle
Kein Problem! Wenn dies die Frage zu Ihrer Zufriedenheit beantworten würde, würde ich es begrüßen, wenn Sie dies als die richtige Antwort auf Ihre Frage wählen könnten. Wenn nicht, lassen Sie mich bitte wissen, wie ich Ihre Frage besser beantworten kann.
Lily Ballard
4
Kein Problem, Kevin. SO verzögert Sie die sofortige Auswahl einer Antwort auf eine Frage, sodass ich etwas später zurückkommen musste. Prost.
Jonathan Sterling
__unsafe_unretained id bself = self;
Caleb
4
@JKLaiho: Sicher, __weakist auch in Ordnung. Wenn Sie sicher wissen, dass das Objekt beim Aufrufen des Blocks nicht außerhalb des Gültigkeitsbereichs liegen kann, __unsafe_unretainedist es etwas schneller, aber im Allgemeinen macht das keinen Unterschied. Wenn Sie verwenden __weak, stellen Sie sicher, dass Sie es in eine __stronglokale Variable werfen und diese auf Nicht-Variablen testen, nilbevor Sie etwas damit tun.
Lily Ballard
2
@Rpranata: Ja. __blockDer Nebeneffekt, nicht zu behalten und freizugeben, war allein auf die Unfähigkeit zurückzuführen, dies richtig zu begründen. Mit ARC hat der Compiler diese Fähigkeit erlangt und __blockbehält und veröffentlicht sie nun. Wenn Sie dies vermeiden müssen, müssen Sie verwenden __unsafe_unretained, wodurch der Compiler angewiesen wird, keine Aufbewahrungen oder Freigaben für den Wert in der Variablen durchzuführen.
Lily Ballard
66

Benutz einfach:

__weak id weakSelf = self;

[someObject someMethodWithBlock:^{
    [weakSelf someOtherMethod];
}];

Weitere Informationen: WWDC 2011 - Blöcke und Grand Central Dispatch in der Praxis .

https://developer.apple.com/videos/wwdc/2011/?id=308

Hinweis: Wenn dies nicht funktioniert, können Sie es versuchen

__weak typeof(self)weakSelf = self;
3lvis
quelle
2
Und hast du es zufällig gefunden :)?
Tieme
2
Sie können das Video hier überprüfen - developer.apple.com/videos/wwdc/2011/…
nanjunda
Können Sie sich in "someOtherMethod" selbst referenzieren? Würde sich das Selbst an diesem Punkt auf das Schwache beziehen oder würde dies auch einen Retentionszyklus erzeugen?
Oren
Hallo @Oren, wenn Sie versuchen, sich in "someOtherMethod" zu referenzieren, erhalten Sie eine Xcode-Warnung. Mein Ansatz bezieht sich nur schwach auf sich selbst.
3lvis
1
Ich habe nur eine Warnung erhalten, wenn ich mich direkt im Block referenziert habe. Das Einfügen in someOtherMethod verursachte keine Warnungen. Liegt das daran, dass xcode nicht intelligent genug ist oder kein Problem darstellt? Würde sich das Verweisen auf self in someOtherMethod bereits auf schwaches Selbst beziehen, da Sie die Methode so aufrufen?
Oren
22

Dies mag offensichtlich sein, aber Sie müssen den hässlichen selfAlias nur dann ausführen, wenn Sie wissen, dass Sie einen Aufbewahrungszyklus erhalten. Wenn der Block nur eine einmalige Sache ist, können Sie die Beibehaltung sicher ignorieren self. Der schlimme Fall ist, wenn Sie den Block beispielsweise als Rückrufschnittstelle haben. Wie hier:

typedef void (^BufferCallback)(FullBuffer* buffer);

@interface AudioProcessor : NSObject {…}
@property(copy) BufferCallback bufferHandler;
@end

@implementation AudioProcessor

- (id) init {
    
    [self setBufferCallback:^(FullBuffer* buffer) {
        [self whatever];
    }];
    
}

Hier macht die API nicht viel Sinn, aber es wäre sinnvoll, wenn man zum Beispiel mit einer Superklasse kommuniziert. Wir behalten den Pufferhandler, der Pufferhandler behält uns. Vergleichen Sie mit so etwas:

typedef void (^Callback)(void);

@interface VideoEncoder : NSObject {…}
- (void) encodeVideoAndCall: (Callback) block;
@end

@interface Foo : NSObject {…}
@property(retain) VideoEncoder *encoder;
@end

@implementation Foo
- (void) somewhere {
    [encoder encodeVideoAndCall:^{
        [self doSomething];
    }];
}

In diesen Situationen mache ich kein selfAliasing. Sie erhalten zwar einen Aufbewahrungszyklus, der Vorgang ist jedoch nur von kurzer Dauer, und der Block wird schließlich nicht mehr gespeichert, wodurch der Zyklus unterbrochen wird. Aber meine Erfahrung mit Blöcken ist sehr gering und es könnte sein, dass selfAliasing auf lange Sicht als Best Practice herauskommt.

Zoul
quelle
6
Guter Punkt. Es ist nur ein Haltezyklus, wenn das Selbst den Block am Leben hält. Bei Blöcken, die niemals kopiert werden, oder Blöcken mit einer garantierten begrenzten Dauer (z. B. dem Abschlussblock für eine UIView-Animation) müssen Sie sich darüber keine Gedanken machen.
Lily Ballard
6
Im Prinzip bist du richtig. Wenn Sie jedoch den Code im Beispiel ausführen würden, würden Sie abstürzen. Blockeigenschaften sollten immer als nicht deklariert copywerden retain. Wenn dies nur retainder Fall ist, gibt es keine Garantie dafür, dass sie vom Stapel entfernt werden. Wenn Sie ihn ausführen, wird er nicht mehr vorhanden sein. (und das Kopieren und bereits kopierte Block wird auf eine Beibehaltung optimiert)
Dave DeLong
Ah, sicher, ein Tippfehler. Ich habe vor einiger Zeit die retainPhase durchlaufen und schnell gemerkt, was du sagst :) Danke!
Zoul
Ich bin mir ziemlich sicher, retaindass Blöcke völlig ignoriert werden (es sei denn, sie haben sich bereits mit vom Stapel entfernt copy).
Steven Fisher
@ Dave DeLong, Nein, es würde nicht abstürzen, da die @ Eigenschaft (Beibehalten) nur für eine Objektreferenz verwendet wird, nicht für den Block. Hier muss überhaupt keine Kopie verwendet werden.
DeniziOS
20

Eine weitere Antwort posten, da dies auch für mich ein Problem war. Ich dachte ursprünglich, ich müsste blockSelf überall dort verwenden, wo es eine Selbstreferenz innerhalb eines Blocks gibt. Dies ist nicht der Fall, sondern nur, wenn das Objekt selbst einen Block enthält. Wenn Sie in diesen Fällen blockSelf verwenden, kann das Objekt freigegeben werden, bevor Sie das Ergebnis aus dem Block zurückerhalten. Wenn es versucht, es aufzurufen, stürzt es ab, sodass Sie möchten, dass self bis zur Antwort erhalten bleibt kommt zurück.

Der erste Fall zeigt, wann ein Aufbewahrungszyklus auftritt, da er einen Block enthält, auf den im Block verwiesen wird:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface ContainsBlock : NSObject 

@property (nonatomic, copy) MyBlock block;

- (void)callblock;

@end 

@implementation ContainsBlock
@synthesize block = _block;

- (id)init {
    if ((self = [super init])) {

        //__block ContainsBlock *blockSelf = self; // to fix use this.
        self.block = ^{
                NSLog(@"object is %@", self); // self retain cycle
            };
    }
    return self;
}

- (void)dealloc {
    self.block = nil;
    NSLog (@"ContainsBlock"); // never called.
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 

@end 

 int main() {
    ContainsBlock *leaks = [[ContainsBlock alloc] init];
    [leaks callblock];
    [leaks release];
}

Im zweiten Fall benötigen Sie blockSelf nicht, da das aufrufende Objekt keinen Block enthält, der einen Aufbewahrungszyklus verursacht, wenn Sie auf self verweisen:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface BlockCallingObject : NSObject 
@property (copy, nonatomic) MyBlock block;
@end

@implementation BlockCallingObject 
@synthesize block = _block;

- (void)dealloc {
    self.block = nil;
    NSLog(@"BlockCallingObject dealloc");
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 
@end

@interface ObjectCallingBlockCallingObject : NSObject 
@end

@implementation ObjectCallingBlockCallingObject 

- (void)doneblock {
    NSLog(@"block call complete");
}

- (void)dealloc {
    NSLog(@"ObjectCallingBlockCallingObject dealloc");
    [super dealloc];
} 

- (id)init {
    if ((self = [super init])) {

        BlockCallingObject *myobj = [[BlockCallingObject alloc] init];
        myobj.block = ^() {
            [self doneblock]; // block in different object than this object, no retain cycle
        };
        [myobj callblock];
        [myobj release];
    }
    return self;
}
@end

int main() {

    ObjectCallingBlockCallingObject *myObj = [[ObjectCallingBlockCallingObject alloc] init];
    [myObj release];

    return 0;
} 
possen
quelle
Dies ist ein weit verbreitetes Missverständnis und kann gefährlich sein, da Blöcke, die beibehalten werden sollten , selfmöglicherweise nicht darauf zurückzuführen sind, dass Personen diesen Fix zu häufig anwenden. Dies ist ein gutes Beispiel für die Vermeidung von Aufbewahrungszyklen in Nicht-ARC-Code, danke für die Veröffentlichung.
Carl Veazey
9

Denken Sie auch daran, dass Aufbewahrungszyklen auftreten können, wenn Ihr Block auf ein anderes Objekt verweist, das dann beibehalten wird self.

Ich bin nicht sicher, ob Garbage Collection bei diesen Aufbewahrungszyklen helfen kann. Wenn das Objekt, das den Block beibehält self(das ich das Serverobjekt nenne), überlebt (das Client-Objekt), wird der Verweis auf das selfInnere des Blocks erst dann als zyklisch betrachtet, wenn das Aufbewahrungsobjekt selbst freigegeben wird. Wenn das Serverobjekt seine Clients weit überlebt, liegt möglicherweise ein erheblicher Speicherverlust vor.

Da es keine sauberen Lösungen gibt, würde ich die folgenden Problemumgehungen empfehlen. Sie können auch eine oder mehrere auswählen, um Ihr Problem zu beheben.

  • Verwenden Sie Blöcke nur zur Vervollständigung und nicht für offene Ereignisse. Verwenden Sie beispielsweise Blöcke für Methoden wie doSomethingAndWhenDoneExecuteThisBlock:und nicht für Methoden wie setNotificationHandlerBlock:. Für die Fertigstellung verwendete Blöcke haben ein bestimmtes Lebensende und sollten von Serverobjekten freigegeben werden, nachdem sie ausgewertet wurden. Dies verhindert, dass der Haltezyklus zu lange lebt, selbst wenn er auftritt.
  • Machen Sie diesen von Ihnen beschriebenen schwachen Referenztanz.
  • Stellen Sie eine Methode zum Bereinigen Ihres Objekts bereit, bevor es freigegeben wird, mit der das Objekt von Serverobjekten "getrennt" wird, die möglicherweise Verweise darauf enthalten. und rufen Sie diese Methode auf, bevor Sie release für das Objekt aufrufen. Diese Methode ist zwar vollkommen in Ordnung, wenn Ihr Objekt nur einen Client hat (oder in einem bestimmten Kontext ein Singleton ist), bricht jedoch zusammen, wenn es mehrere Clients hat. Sie besiegen hier im Grunde den Retain-Counting-Mechanismus. das ist vergleichbar mit anrufen deallocstatt release.

Wenn Sie ein Serverobjekt schreiben, nehmen Sie Blockargumente nur zur Vervollständigung. Akzeptieren Sie keine Blockargumente für Rückrufe wie z setEventHandlerBlock:. Greifen Sie stattdessen auf das klassische Delegatenmuster zurück: Erstellen Sie ein formales Protokoll und geben Sie eine setEventDelegate:Methode bekannt. Behalten Sie den Delegierten nicht. Wenn Sie nicht einmal ein formelles Protokoll erstellen möchten, akzeptieren Sie einen Selektor als Delegatenrückruf.

Und schließlich sollte dieses Muster Alarm auslösen:

- (nichtiger) Dealloc {
    [myServerObject releaseCallbackBlocksForObject: self];
    ...
}}

Wenn Sie versuchen, Blöcke zu lösen, auf die selfvon innen verwiesen wird dealloc, sind Sie bereits in Schwierigkeiten. deallocMöglicherweise wird es aufgrund des durch Verweise im Block verursachten Aufbewahrungszyklus niemals aufgerufen. Dies bedeutet, dass Ihr Objekt einfach leckt, bis die Zuordnung des Serverobjekts aufgehoben wird.

Dave R.
quelle
GC hilft, wenn Sie es __weakangemessen verwenden.
tc.
Das Verfolgen der Speicherbereinigung kann sich natürlich mit Aufbewahrungszyklen befassen. Beibehaltungszyklen sind nur ein Problem für Referenzzählumgebungen
newacct
Damit jeder weiß, wurde die Speicherbereinigung in OS X 10.8 zugunsten der automatischen Referenzzählung (ARC) nicht mehr unterstützt und soll in einer zukünftigen Version von OS X ( developer.apple.com/library/mac/#releasenotes) entfernt werden / ObjectiveC /… ).
Ricardo Sanchez-Saez
1

__block __unsafe_unretainedIn Kevins Beitrag vorgeschlagene Modifikatoren können zu einer Ausnahme für fehlerhaften Zugriff führen, wenn ein Block in einem anderen Thread ausgeführt wird. Es ist besser, nur den Modifikator __block für die temporäre Variable zu verwenden und sie nach der Verwendung auf Null zu setzen.

__block SomeType* this = self;
[someObject messageWithBlock:^{
  [this doSomething]; // here would be BAD_ACCESS in case of __unsafe_unretained with
                      //  multithreading and self was already released
  this = nil;
}];
b1gbr0
quelle
Wäre es nicht wirklich sicherer, nur __weak anstelle von __block zu verwenden, um zu vermeiden, dass die Variable nach ihrer Verwendung gelöscht werden muss? Ich meine, diese Lösung ist großartig, wenn Sie andere Arten von Zyklen unterbrechen möchten, aber ich sehe sicherlich keine besonderen Vorteile für "Selbst" -Rückhaltezyklen.
Ale0xB
Sie können __weak nicht verwenden, wenn Ihr Plattformziel iOS 4.x ist. Manchmal muss der Code im Block auch für das gültige Objekt ausgeführt werden, nicht für nil.
b1gbr0
1

Sie können die Bibliothek libextobjc verwenden. Es ist sehr beliebt, es wird zum Beispiel in ReactiveCocoa verwendet. https://github.com/jspahrsummers/libextobjc

Es bietet 2 Makros @weakify und @strongify, so dass Sie haben können:

@weakify(self)
[someObject messageWithBlock:^{
   @strongify(self)
   [self doSomething]; 
}];

Dies verhindert eine direkte starke Referenz, so dass wir nicht in einen Retentionszyklus für uns selbst geraten. Außerdem verhindert es, dass das Selbst auf halbem Weg gleich Null wird, verringert jedoch die Anzahl der Retentionen ordnungsgemäß. Mehr unter diesem Link: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html

Yuri Solodkin
quelle
1
Bevor der vereinfachte Code angezeigt wird, ist es besser zu wissen, was dahinter steckt. Jeder sollte die beiden tatsächlichen Codezeilen kennen.
Alex Cio
0

Wie wäre es damit?

- (void) foo {
     __weak __block me = self;

     myBlock = ^ {
        [[me someProp] someMessage];
     }
     ...
 }

Ich bekomme keine Compiler-Warnung mehr.

Καrτhικ
quelle
-1

Block: Ein Aufbewahrungszyklus wird ausgeführt, da er einen Block enthält, auf den im Block verwiesen wird. Wenn Sie die Blockkopie erstellen und eine Elementvariable verwenden, bleibt self erhalten.

Yijiankaka
quelle