Was bedeutet das Schlüsselwort "__block"?

445

Was genau macht das __block Schlüsselwort in Objective-C? Ich weiß, dass Sie damit Variablen innerhalb von Blöcken ändern können, aber ich würde gerne wissen ...

  1. Was genau sagt es dem Compiler?
  2. Tut es noch etwas?
  3. Wenn das alles ist, warum wird es dann überhaupt benötigt?
  4. Ist es irgendwo in den Dokumenten? (Ich kann es nicht finden).
mjisrawi
quelle
3
Überprüfen Sie hier und den Abschnitt "Blöcke und Variablen".
1
@ Code Monkey: Ich habe speziell nach dem Schlüsselwort gefragt, nicht nach der Syntax im Allgemeinen. Denken Sie also nicht, dass es wirklich ein Duplikat ist.
Mjisrawi
3
@ Code Monkey: Nein, dies ist kein Duplikat. Die Frage, die Sie erwähnen, spricht überhaupt nicht darüber __block.
DarkDust
3
Und wenn sich jemand fragt, wie Objective- Cs in Swift__block übersetzt werden sollen: „Closures [in Swift] haben eine ähnliche Erfassungssemantik wie Blöcke [in Objective-C], unterscheiden sich jedoch in einer wesentlichen Hinsicht: Variablen sind eher veränderlich als kopierbar. Mit anderen Worten, das Verhalten von __block in Objective-C ist das Standardverhalten für Variablen in Swift. “ Aus Apples Buch: Verwenden von Swift mit Kakao und Objective-C (Swift 2.2).
Jari Keinänen

Antworten:

542

Es teilt dem Compiler mit, dass jede von ihm markierte Variable auf besondere Weise behandelt werden muss, wenn sie in einem Block verwendet wird. Normalerweise werden Variablen und deren Inhalt, die auch in Blöcken verwendet werden, kopiert, sodass Änderungen an diesen Variablen nicht außerhalb des Blocks angezeigt werden. Wenn sie mit markiert sind __block, sind die innerhalb des Blocks vorgenommenen Änderungen auch außerhalb des Blocks sichtbar.

Ein Beispiel und weitere Informationen finden Sie unter Der __block-Speichertyp in den Themen zur Blockprogrammierung von Apple .

Das wichtige Beispiel ist dieses:

extern NSInteger CounterGlobal;
static NSInteger CounterStatic;

{
    NSInteger localCounter = 42;
    __block char localCharacter;

    void (^aBlock)(void) = ^(void) {
        ++CounterGlobal;
        ++CounterStatic;
        CounterGlobal = localCounter; // localCounter fixed at block creation
        localCharacter = 'a'; // sets localCharacter in enclosing scope
    };

    ++localCounter; // unseen by the block
    localCharacter = 'b';

    aBlock(); // execute the block
    // localCharacter now 'a'
}

In diesem Beispiel werden beide localCounterund localCharactergeändert, bevor der Block aufgerufen wird. Innerhalb des Blocks localCharacterwäre jedoch dank des __blockSchlüsselworts nur die Änderung von sichtbar . Umgekehrt kann der Block geändert werden, localCharacterund diese Änderung ist außerhalb des Blocks sichtbar.

DarkDust
quelle
8
Hervorragende, prägnante Erklärung und ein sehr hilfreiches Beispiel. Vielen Dank!
Evan Stone
1
Wie ändert der aBlock localCounter? Es scheint nur CounterGlobal zu modifizieren. Danke
CommaToast
8
Es ändert sich nicht localCounter, aber es ändert sich localCharacter. Achten Sie auch auf den Wert localCounterim Block: Es ist 42, obwohl die Variable erhöht wird, bevor der Block aufgerufen wird, aber nachdem der Block erstellt wurde (dann wurde der Wert "erfasst").
DarkDust
1
Das ist jedoch eine hilfreiche Erklärung. Können Sie erklären, was in Ihrer Erklärung als widersprüchliche Aussagen erscheint? Sie sagen oben, dass "aBlock modifiziert ... localCounter" und dann in den Kommentaren sagen Sie, dass [[aBlock] localCounter NICHT modifiziert. " Welches ist es? Wenn es "nicht geändert" ist, sollte Ihre Antwort dann bearbeitet werden?
Praxiteles
2
Im Allgemeinen werden Variablen ohne __block nach Wert erfasst und beim Erstellen des Blocks in die "Umgebung" des Blocks gepackt. __Block-Variablen werden jedoch nicht erfasst, wenn sie innerhalb oder außerhalb eines Blocks verwendet werden. Sie werden unverändert referenziert.
Jchnxu
27

@bbum behandelt Blöcke in einem Blog-Beitrag ausführlich und berührt den Speichertyp __block.

__block ist ein bestimmter Speichertyp

Genau wie statisch, automatisch und flüchtig ist __block ein Speichertyp. Es teilt dem Compiler mit, dass der Speicher der Variablen anders verwaltet werden soll.

...

Bei __block-Variablen bleibt der Block jedoch nicht erhalten. Es liegt an Ihnen, diese nach Bedarf aufzubewahren und freizugeben.
...

In Anwendungsfällen __blockwird manchmal verwendet, um Aufbewahrungszyklen zu vermeiden, da das Argument nicht beibehalten wird. Ein häufiges Beispiel ist die Verwendung von Selbst.

//Now using myself inside a block will not 
//retain the value therefore breaking a
//possible retain cycle.
__block id myself = self;
Joe
quelle
In diesem Beitrag finden Sie weitere Informationen zum Problem des Aufbewahrungszyklus : benscheirman.com/2012/01/… . Würde auch __weakin diesem speziellen Fall ausreichen? Es ist vielleicht etwas klarer ...
Hari Karam Singh
17
Schließlich ist die Behauptung, dass __block verwendet werden kann, um starke Referenzzyklen (auch als Beibehaltungszyklen bezeichnet) zu vermeiden, in einem ARC-Kontext eindeutig falsch. Aufgrund der Tatsache, dass in ARC __block die Variable stark referenziert wird, ist es tatsächlich wahrscheinlicher, dass sie verursacht wird. stackoverflow.com/a/19228179/189006
Krishnan
10

Wenn Sie __block nicht verwenden, kopiert der Block normalerweise die Variable (behält sie bei). Selbst wenn Sie die Variable ändern, hat der Block Zugriff auf das alte Objekt.

NSString* str = @"hello";
void (^theBlock)() = ^void() {
    NSLog(@"%@", str);
};
str = @"how are you";
theBlock(); //prints @"hello"

In diesen 2 Fällen benötigen Sie __block:

1.Wenn Sie die Variable innerhalb des Blocks ändern möchten und erwarten, dass sie außerhalb sichtbar ist:

__block NSString* str = @"hello";
void (^theBlock)() = ^void() {
    str = @"how are you";
};
theBlock();
NSLog(@"%@", str); //prints "how are you"

2.Wenn Sie die Variable ändern möchten, nachdem Sie den Block deklariert haben und erwarten, dass der Block die Änderung sieht:

__block NSString* str = @"hello";
void (^theBlock)() = ^void() {
    NSLog(@"%@", str);
};
str = @"how are you";
theBlock(); //prints "how are you"
Hamid Vakilian
quelle
8

__block ist ein Speicherqualifizierer, der auf zwei Arten verwendet werden kann:

  1. Markiert, dass sich eine Variable in einem Speicher befindet, der vom lexikalischen Bereich der ursprünglichen Variablen und allen in diesem Bereich deklarierten Blöcken gemeinsam genutzt wird. Und clang generiert eine Struktur, um diese Variable darzustellen, und verwendet diese Struktur als Referenz (nicht als Wert).

  2. In MRC kann __block verwendet werden, um zu vermeiden, dass Objektvariablen, die ein Block erfasst, beibehalten werden. Beachten Sie, dass dies bei ARC nicht funktioniert. In ARC sollten Sie stattdessen __weak verwenden.

Ausführliche Informationen finden Sie in Apple Doc .

Mindy
quelle
6

__blockist ein Speichertyp, der verwendet wird, um Variablen im Bereich veränderbar zu machen. Wenn Sie eine Variable mit diesem Bezeichner deklarieren, wird ihre Referenz an Blöcke übergeben, die keine schreibgeschützte Kopie sind. Weitere Informationen finden Sie unter Blockprogrammierung in iOS

Mithilesh
quelle
2

hoffe das wird dir helfen

Nehmen wir an, wir haben einen Code wie:

{
     int stackVariable = 1;

     blockName = ^()
     {
      stackVariable++;
     }
}

Es wird ein Fehler wie "Variable ist nicht zuweisbar" ausgegeben, da die Stapelvariablen im Block standardmäßig unveränderlich sind.

Durch Hinzufügen von __block (Speichermodifikator) vor der Deklaration wird es innerhalb des Blocks veränderbar, d. h __block int stackVariable=1;

Anurag Bhakuni
quelle
1

Aus der Block Language Spec :

Zusätzlich zum neuen Blocktyp führen wir auch ein neues Speicherqualifikationsmerkmal, __block, für lokale Variablen ein. [testme: eine __block-Deklaration innerhalb eines Blockliteral] Der __block-Speicherqualifizierer schließt sich gegenseitig für die vorhandenen lokalen Speicherqualifizierer auto, register und static aus. [testme] Durch __block qualifizierte Variablen verhalten sich so, als ob sie sich im zugewiesenen Speicher befinden, und dieser Speicher ist wird nach der letzten Verwendung dieser Variablen automatisch wiederhergestellt. Eine Implementierung kann eine Optimierung wählen, bei der der Speicher anfänglich automatisch ist und erst nach einer Blockkopie eines referenzierenden Blocks in den zugewiesenen (Heap-) Speicher "verschoben" wird. Solche Variablen können wie normale Variablen mutiert sein.

In dem Fall, in dem eine __block-Variable ein Block ist, muss angenommen werden, dass sich die __block-Variable im zugewiesenen Speicher befindet und als solche auf einen Block verweist, der sich ebenfalls im zugewiesenen Speicher befindet (dass dies das Ergebnis einer Block_copy-Operation ist). Trotzdem ist keine Block_copy oder Block_release vorgesehen, wenn eine Implementierung einen anfänglichen automatischen Speicher für Blöcke bereitstellt. Dies liegt an der inhärenten Race-Bedingung von möglicherweise mehreren Threads, die versuchen, die gemeinsam genutzte Variable zu aktualisieren, und an der Notwendigkeit einer Synchronisierung, um ältere Werte zu entsorgen und neue zu kopieren. Eine solche Synchronisation würde den Rahmen dieser Sprachspezifikation sprengen.

Einzelheiten zu , was eine __block Variable kompilieren sollte, finden Sie in der Block - Implementierung Spec , Abschnitt 2.3.

Martin Gordon
quelle
Ihre Links sind beide tot
Ben Leggiero
Dies ist keine wirkliche Antwort und könnte konkretisiert oder entfernt werden. Vielen Dank!
Dan Rosenstark
0

Dies bedeutet, dass die Variable, für die es sich um ein Präfix handelt, innerhalb eines Blocks verwendet werden kann.

Rich Allen
quelle