web-dev-qa-db-de.com

Halte den Zyklus mit Blöcken auf "Selbst"

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

Was ich gehört habe ist, dass, da Blöcke lokale Variablen erfassen, die in ihnen als const Kopien referenziert werden, die Verwendung von self in einem Block zu einem Aufbewahrungszyklus führen kann, falls dieser Block kopiert werden sollte. Also sollen wir __block um den Block zu zwingen, direkt mit self umzugehen, anstatt 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)?

165

Genau genommen hat die Tatsache, dass es sich um eine konstante Kopie handelt, nichts mit diesem Problem zu tun. Blöcke behalten alle Objektwerte bei, die erfasst werden, wenn sie erstellt werden. Es kommt einfach so vor, dass die Problemumgehung für das const-copy-Problem mit der Problemumgehung für das retain-Problem identisch ist. nämlich mit dem __block Speicherklasse für die Variable.

In jedem Fall gibt es hier keine echte Alternative, um Ihre Frage zu beantworten. Wenn Sie eine eigene blockbasierte API entwerfen und dies sinnvoll ist, kann dem Block der Wert self als Argument übergeben werden. Leider ist dies für die meisten APIs nicht sinnvoll.

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


Nachtrag: Beim Kompilieren als ARC wird __block bricht nicht mehr ab, behält Zyklen. Wenn Sie für ARC kompilieren, müssen Sie __weak oder __unsafe_unretained stattdessen.

168
Lily Ballard

Benutz einfach:

__weak id weakSelf = self;

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

Weitere Informationen: WWDC 2011 - Blocks und Grand Central Dispatch in der Praxis .

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

Hinweis: Wenn das nicht funktioniert, kannst du es versuchen

__weak typeof(self)weakSelf = self;
65
3lvis

Dies mag offensichtlich sein, aber Sie müssen den hässlichen self -Alias ​​nur dann ausführen, wenn Sie wissen, dass Sie einen Beibehaltungszyklus erhalten. Wenn es sich bei dem Block nur um eine einmalige Aktion handelt, können Sie die Aufbewahrung von self meines Erachtens ignorieren. Der schlimme Fall ist, wenn Sie den Baustein beispielsweise als Callback-Schnittstelle 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 zum Beispiel sinnvoll, mit einer Superklasse zu kommunizieren. 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 self -Aliasing. Sie erhalten zwar einen Beibehaltungszyklus, der Vorgang ist jedoch nur von kurzer Dauer und der Block hat irgendwann keinen Speicher mehr und bricht den Zyklus ab. Aber meine Erfahrung mit Blöcken ist sehr gering und es könnte sein, dass self -Aliasing auf lange Sicht als Best Practice herauskommt.

22
zoul

Poste eine andere Antwort, weil dies auch für mich ein Problem war. Ich dachte ursprünglich, ich müsste blockSelf überall dort verwenden, wo sich eine Selbstreferenz innerhalb eines Blocks befindet. Dies ist nicht der Fall, sondern nur, wenn das Objekt selbst einen Block enthält. Und wenn Sie in diesen Fällen blockSelf verwenden, kann das Objekt freigegeben werden, bevor Sie das Ergebnis vom Block zurückerhalten, und dann stürzt es ab, wenn es versucht, es aufzurufen. Sie möchten also, dass self bis zur Antwort erhalten bleibt kommt zurück.

Der erste Fall zeigt, wann ein Haltezyklus auftritt, weil 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];
}

Sie benötigen blockSelf im zweiten Fall nicht, da das aufrufende Objekt keinen Block enthält, der einen Beibehaltungszyklus 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;
} 
19
possen

Denken Sie auch daran, dass Haltezyklen auftreten können, wenn sich Ihr Block auf bezieht ein weiterer Objekt, das dann self behält.

Ich bin nicht sicher, ob die Garbage Collection in diesen Aufbewahrungszyklen hilfreich sein kann. Wenn das Objekt, das den Block enthält (das ich Server-Objekt nennen werde), self (das Client-Objekt) überlebt, wird der Verweis auf self innerhalb des Blocks erst als zyklisch betrachtet, wenn das Objekt beibehalten wird selbst wird freigegeben. 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 einen oder mehrere auswählen, um Ihr Problem zu beheben.

  • Verwenden Sie Blöcke nur für fertigstellungund nicht für unbefristete Veranstaltungen. Verwenden Sie beispielsweise Blöcke für Methoden wie doSomethingAndWhenDoneExecuteThisBlock: und nicht 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 Retentionszyklus zu lange dauert, selbst wenn er auftritt.
  • Tun Sie diesen Tanz mit schwachen Referenzen, den Sie beschrieben haben.
  • Stellen Sie eine Methode zum Bereinigen Ihres Objekts vor dessen Freigabe bereit, 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 völlig in Ordnung, wenn Ihr Objekt nur einen Client hat (oder in einem bestimmten Kontext ein Singleton ist), funktioniert aber nicht, wenn es mehrere Clients hat. Sie besiegen hier im Grunde den Retain-Counting-Mechanismus. Dies entspricht dem Aufruf von dealloc anstelle von release.

Wenn Sie ein Serverobjekt schreiben, verwenden Sie Blockargumente nur zur Vervollständigung. Akzeptieren Sie keine Blockargumente für Rückrufe wie setEventHandlerBlock:. Greifen Sie stattdessen auf das klassische Delegatenmuster zurück: Erstellen Sie ein formelles Protokoll und machen Sie Werbung für ein setEventDelegate: Methode. Bewahren Sie den Delegierten nicht auf. Wenn Sie nicht einmal ein formelles Protokoll erstellen möchten, akzeptieren Sie einen Selektor als Rückruf für Stellvertreter.

Und zu guter Letzt sollte dieses Muster einen Alarm auslösen:

 - (nichtige) Freigabe {
 [myServerObject releaseCallbackBlocksForObject: self]; 
 ... 
} 

Wenn Sie versuchen, Blöcke, die möglicherweise auf self verweisen, von innen dealloc zu lösen, sind Sie bereits in Schwierigkeiten. dealloc wird möglicherweise nie aufgerufen, da der Aufbewahrungszyklus durch Verweise im Block verursacht wird. Dies bedeutet, dass Ihr Objekt nur einen Leckverlust aufweist, bis die Zuordnung des Serverobjekts aufgehoben wird.

9
Dave R

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

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

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

Dies verhindert einen direkten starken Bezug, so dass wir nicht in einen Rückhaltezyklus für uns selbst geraten. Außerdem verhindert es, dass das Selbst auf halbem Weg null wird, verringert jedoch die Anzahl der Beibehaltungen ordnungsgemäß. Mehr in diesem Link: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html

1
Yuri Solodkin

__block __unsafe_unretained Modifikatoren, die in Kevins Beitrag vorgeschlagen wurden, können zu einer Ausnahme für fehlerhaften Zugriff führen, wenn der Block in einem anderen Thread ausgeführt wird. Es ist besser, nur den Modifikator __ block für die temporäre Variable zu verwenden und ihn 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;
}];
1
b1gbr0

Wie wäre es damit?

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

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

Ich bekomme keine Warnung des Compilers mehr.

0
Καrτhικ