Immer schwache Referenz des Selbst in Block in ARC übergeben?

252

Ich bin etwas verwirrt über die Blockverwendung in Objective-C. Ich verwende derzeit ARC und habe ziemlich viele Blöcke in meiner App, die sich derzeit immer auf selfdie schwache Referenz beziehen. selfKann dies die Ursache dafür sein, dass diese Blöcke erhalten bleiben und nicht freigegeben werden? Die Frage ist, sollte ich immer eine weakReferenz von selfin 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);
    }
}
the_critic
quelle
Wenn Sie einen ausführlichen Diskurs zu diesem Thema wünschen,
David H

Antworten:

721

Es hilft, sich nicht auf die strongoder einen weakTeil der Diskussion zu konzentrieren. Konzentrieren Sie sich stattdessen auf den Fahrradteil .

A beibehält Zyklus eine Schleife ist, was geschieht , wenn das Objekt A Objekt B behält, und Objekt B behält Objekt A. In dieser Situation , wenn ein Objekt freigegeben wird:

  • Objekt A wird nicht freigegeben, da Objekt B einen Verweis darauf enthält.
  • Objekt B wird jedoch niemals freigegeben, solange Objekt A einen Verweis darauf hat.
  • Objekt A wird jedoch niemals freigegeben, da Objekt B einen Verweis darauf enthält.
  • Ad infinitum

Somit bleiben diese beiden Objekte während der gesamten Laufzeit des Programms im Speicher, obwohl sie freigegeben werden sollten, wenn alles ordnungsgemäß funktioniert.

Wir machen uns also Sorgen, Zyklen beizubehalten , und es gibt nichts an Blöcken an und für sich, die diese Zyklen erzeugen. Dies ist zum Beispiel kein Problem:

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

Der Block behält den Block bei, behält ihn selfjedoch selfnicht bei. Wenn der eine oder andere freigegeben wird, wird kein Zyklus erstellt und alles wird freigegeben, wie es sollte.

Wo Sie in Schwierigkeiten geraten, ist so 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 strongVerweis auf den Block. Und der Block hat einen impliziten starken Bezug zu self. Das ist ein Zyklus, und jetzt wird keines der Objekte ordnungsgemäß freigegeben.

Da in einer solchen Situation self per Definition bereits ein strongVerweis auf den Block vorhanden ist, ist es normalerweise am einfachsten, einen explizit schwachen Verweis auf selfden zu verwendenden Block zu erstellen:

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

Dies sollte jedoch nicht das Standardmuster sein, dem Sie beim Umgang mit aufrufenden Blöcken folgenself ! Dies sollte nur verwendet werden, um einen Haltezyklus zwischen self und dem Block zu unterbrechen. Wenn Sie dieses Muster überall anwenden würden, laufen Sie Gefahr, einen Block an etwas zu übergeben, das ausgeführt wurde, nachdem selfdie Zuordnung 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];
}];
Jemmons
quelle
2
Ich bin nicht sicher, ob A B behalten, B A behalten einen unendlichen Zyklus machen wird. Aus der Perspektive des Referenzzählers ist der Referenzzähler von A und B 1. Was einen Retentionszyklus für diese Situation verursacht, ist, wenn es keine andere Gruppe gibt, die eine starke Referenz von A und B außerhalb hat - dies bedeutet, dass wir diese beiden Objekte (wir) nicht erreichen können kann A nicht steuern, um B freizugeben und umgekehrt), daher verweisen A und B aufeinander, um sich beide am Leben zu erhalten.
Danyun Liu
@Danyun Es stimmt zwar, dass ein Aufbewahrungszyklus zwischen A und B nicht wiederhergestellt werden kann, bis alle anderen Verweise auf diese Objekte freigegeben wurden, aber das macht ihn nicht weniger zu einem Zyklus. Nur weil ein bestimmter Zyklus wiederherstellbar ist, heißt das noch lange nicht, dass er in Ihrem Code enthalten ist. Retain-Zyklen riechen nach schlechtem Design.
Jemmons
@jemmons Ja, wir sollten ein Retain-Cycle-Design immer so weit wie möglich vermeiden.
Danyun Liu
1
@ Master Es ist unmöglich für mich zu sagen. Dies hängt vollständig von der Implementierung Ihrer -setCompleteionBlockWithSuccess:failure:Methode ab. Wenn paginatores sich jedoch im Besitz von ViewControllerbefindet und diese Blöcke nicht aufgerufen werden, nachdem ViewControllersie freigegeben wurden, ist die Verwendung einer __weakReferenz der sichere Schritt (da selfdas Objekt gehört, dem die Blöcke gehören, und daher wahrscheinlich immer noch vorhanden ist, wenn die Blöcke es aufrufen obwohl sie es nicht behalten). Aber das sind viele "Wenn". Es kommt wirklich darauf an, was das tun soll.
Jemmons
1
@Jai Nein, und dies ist der Kern des Speicherverwaltungsproblems mit Blöcken / Schließungen. Objekte werden freigegeben, wenn ihnen nichts gehört. MyObjectund SomeOtherObjectbeide besitzen den Block. Aber weil die Referenz Blockade MyObjectist weak, wird der Block nicht selbst MyObject. So , während der Block garantiert wird solange bestehen entweder MyObject oder SomeOtherObject existieren, gibt es keine Garantie , dass MyObjectsolange der Block nicht vorhanden ist wird. MyObjectkann vollständig freigegeben werden und solange SomeOtherObjectder Block noch vorhanden ist, bleibt der Block bestehen.
Jemmons
26

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 und innerhalb von Capture Self erfassen, wird ein Aufbewahrungszyklus erstellt.

Leo Natan
quelle
Nun, ich führe den Block nur als Rückruf aus und möchte nicht, dass das Selbst freigegeben wird. Aber es scheint, als würde ich Aufbewahrungszyklen erstellen, da der betreffende View Controller nicht freigegeben wird ...
the_critic
1
Wenn Sie beispielsweise ein Balkenschaltflächenelement haben, das vom Ansichtscontroller beibehalten wird, und Sie sich in diesem Block stark selbst erfassen, wird ein Aufbewahrungszyklus ausgeführt.
Leo Natan
1
@ MartinE. Sie sollten Ihre Frage nur mit Codebeispielen aktualisieren. Leo hat ganz recht (+1), dass es nicht unbedingt einen starken Referenzzyklus verursacht, aber es kann sein, je nachdem, wie Sie diese Blöcke verwenden. Wir können Ihnen leichter helfen, wenn Sie Codefragmente bereitstellen.
Rob
@LeoNatan Ich verstehe den Begriff der Aufbewahrungszyklen, bin mir aber nicht ganz sicher, was in Blöcken passiert, was mich ein wenig verwirrt
the_critic
Im obigen Code wird Ihre Selbstinstanz freigegeben, sobald der Vorgang abgeschlossen und der Block freigegeben ist. Sie sollten nachlesen, wie Blöcke funktionieren und was und wann sie in ihrem Umfang erfasst werden.
Leo Natan
26

Ich stimme @jemmons voll und ganz zu:

Dies sollte jedoch nicht das Standardmuster sein, dem Sie beim Umgang mit Blöcken folgen, die sich selbst aufrufen! Dies sollte nur verwendet werden, um einen Haltezyklus zwischen self und dem Block zu unterbrechen. Wenn Sie dieses Muster überall anwenden würden, 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];
}];

Um dieses Problem zu lösen, kann man eine starke Referenz über das weakSelfInnere des Blocks definieren:

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];
Ilker Baltaci
quelle
3
Würde strongSelf nicht den Referenzzähler für schwachSelf erhöhen? So einen Retain-Zyklus erstellen?
mskw
12
Beibehaltungszyklen sind nur wichtig, wenn sie im statischen Zustand des Objekts vorhanden sind. Während der Code ausgeführt wird und sein Status im Fluss ist, sind mehrere und möglicherweise redundante Aufbewahrungen in Ordnung. In Bezug auf dieses Muster kann das Erfassen einer starken Referenz dort nichts für den Fall tun, dass die Zuordnung aufgehoben wird, bevor der Block ausgeführt wird. Dies kann dennoch passieren. Es stellt sicher, dass self während der Ausführung des Blocks nicht freigegeben wird . Dies ist wichtig, wenn der Block selbst asynchrone Operationen ausführt und ein Fenster dafür gibt.
Pierre Houston
@smallduck, deine Erklärung ist großartig. Jetzt verstehe ich das besser. Bücher, die dies nicht behandelt haben, danke.
Huibin Zhang
5
Dies ist kein gutes Beispiel für strongSelf, da das explizite Hinzufügen von strongSelf genau das ist, was die Laufzeit sowieso tun würde: In der Zeile doSomething wird eine starke Referenz für die Dauer des Methodenaufrufs verwendet. Wenn schwachSelf bereits ungültig gemacht wurde, ist die starke Referenz null und der Methodenaufruf ist ein No-Op. StrongSelf hilft, wenn Sie über eine Reihe von Operationen verfügen oder auf ein Mitgliedsfeld ( ->) zugreifen , in dem Sie sicherstellen möchten, dass Sie tatsächlich eine gültige Referenz erhalten, und diese kontinuierlich über die gesamte Gruppe von Operationen hinweg aufbewahren, z. B.if ( strongSelf ) { /* several operations */ }
Ethan
19

Wie Leo betont, würde der Code, den Sie Ihrer Frage hinzugefügt haben, keinen starken Referenzzyklus vorschlagen (auch bekannt als Zyklus beibehalten). Ein betriebsbedingtes Problem, das einen starken Referenzzyklus verursachen könnte, wäre, wenn die Operation nicht freigegeben wird. Ihr Code-Snippet weist zwar darauf hin, dass Sie Ihren Vorgang nicht als gleichzeitig definiert haben, aber wenn Sie dies getan haben, wird er nicht freigegeben, wenn Sie nie gepostet isFinishedhaben oder wenn Sie zirkuläre Abhängigkeiten oder ähnliches hatten. Und wenn der Vorgang nicht freigegeben wird, wird auch der View Controller nicht freigegeben. Ich würde vorschlagen, einen Haltepunkt oder NSLogdie deallocMethode Ihres Vorgangs hinzuzufügen und zu bestätigen, dass dieser aufgerufen wird.

Du sagtest:

Ich verstehe den Begriff der Beibehaltungszyklen, 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, mit denen Sie vertraut sind. Ein Block behält starke Verweise auf Objekte bei, die innerhalb des Blocks angezeigt werden, und gibt diese starken Verweise erst frei, wenn der Block selbst freigegeben wird. Wenn also Blockreferenzen selfoder sogar nur eine Instanzvariable von referenzieren self, die einen starken Bezug zu self beibehält, wird dies erst aufgelöst, wenn der Block freigegeben wird (oder in diesem Fall, bis die NSOperationUnterklasse freigegeben wird).

Weitere Informationen finden Sie im Abschnitt Vermeiden starker Referenzzyklen beim Erfassen von Selbst im Dokument Programmieren mit Objective-C: Arbeiten mit Blöcken .

Wenn Ihr View Controller immer noch nicht freigegeben wird, müssen Sie lediglich feststellen, wo sich die ungelöste starke Referenz befindet (vorausgesetzt, Sie haben bestätigt, dass die Zuordnung NSOperationaufgehoben wird). Ein häufiges Beispiel ist die Verwendung einer Wiederholung NSTimer. Oder ein benutzerdefiniertes delegateoder anderes Objekt, das fälschlicherweise eine strongReferenz verwaltet. Sie können häufig Instrumente verwenden, um festzustellen, wo Objekte ihre starken Referenzen erhalten, z.

Referenzzählungen in Xcode 6 aufzeichnen

Oder in Xcode 5:

Referenzzählungen in Xcode 5 aufzeichnen

rauben
quelle
1
Ein anderes Beispiel wäre, wenn die Operation im Blockersteller beibehalten und nach Abschluss nicht freigegeben wird. +1 auf das schöne schreiben!
Leo Natan
@LeoNatan Einverstanden, obwohl das Code-Snippet es als lokale Variable darstellt, die freigegeben würde, wenn er ARC verwendet. Aber du hast ganz recht!
Rob
1
Ja, ich habe nur ein Beispiel gegeben, wie das OP in der anderen Antwort gefordert hat.
Leo Natan
Übrigens hat Xcode 8 "Debug Memory Graph", was eine noch einfachere Möglichkeit ist, starke Verweise auf Objekte zu finden, die noch nicht freigegeben wurden. Siehe stackoverflow.com/questions/30992338/… .
Rob
0

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 im Dokument

Danyun Liu
quelle
-2

So können Sie das Selbst innerhalb des Blocks verwenden:

// Aufruf des Blocks

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


};
Ranjeet Singh
quelle