Co oznacza słowo kluczowe „__block”?

446

Co dokładnie oznacza __blocksłowo kluczowe w celu C? Wiem, że pozwala modyfikować zmienne w blokach, ale chciałbym wiedzieć ...

  1. Co dokładnie mówi kompilatorowi?
  2. Czy robi coś jeszcze?
  3. Jeśli to wszystko, co robi, to dlaczego jest potrzebne?
  4. Czy jest to gdzieś w dokumentacji? (Nie mogę tego znaleźć).
mjisrawi
źródło
3
sprawdź tutaj i sekcję „Bloki i zmienne”.
1
@Code Monkey: Pytałem konkretnie o słowo kluczowe, a nie o składnię w ogóle. Więc nie myśl, że to naprawdę duplikat.
mjisrawi
3
@Code Monkey: Nie, to nie jest duplikat. Pytanie, o którym wspominasz, wcale nie mówi __block.
DarkDust
3
A jeśli ktoś zastanawia się, jak Cel C __blockpowinien się przełożyć na Swift: ”Zamknięcia [w Swift] mają podobną semantykę przechwytywania jak bloki [w Celu C], ale różnią się w jeden kluczowy sposób: Zmienne można modyfikować, a nie kopiować. Innymi słowy, zachowanie __block w Objective-C jest domyślnym zachowaniem zmiennych w Swift. ” Z książki Apple: Używanie Swift z kakao i Objective-C (Swift 2.2).
Jari Keinänen

Odpowiedzi:

543

Informuje kompilator, że każda zmienna przez niego oznaczona musi być traktowana w specjalny sposób, gdy jest używana wewnątrz bloku. Zwykle zmienne i ich zawartość, które są również używane w blokach, są kopiowane, dlatego wszelkie modyfikacje tych zmiennych nie są wyświetlane poza blokiem. Po ich oznaczeniu __blockmodyfikacje dokonane wewnątrz bloku są również widoczne poza nim.

Aby uzyskać przykład i więcej informacji, zobacz Typ pamięci __block w tematach programowania bloków Apple .

Ważnym przykładem jest ten:

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'
}

W tym przykładzie, jak localCounteri localCharactersą modyfikowane przed blokiem jest tzw. Jednak wewnątrz bloku localCharacterwidoczna będzie tylko modyfikacja , dzięki __blocksłowu kluczowemu. I odwrotnie, blok może modyfikować localCharacteri ta modyfikacja jest widoczna na zewnątrz bloku.

DarkDust
źródło
8
Doskonałe, zwięzłe wyjaśnienie i bardzo pomocny przykład. Dzięki!
Evan Stone,
1
Jak aBlock modyfikuje localCounter? Wydaje się to tylko modyfikować CounterGlobal. Dzięki
CommaToast
8
Nie modyfikuje localCounter, ale modyfikuje localCharacter. Zwróć także uwagę na wartość, localCounterktóra ma w bloku: jest 42, mimo że zmienna jest zwiększana przed wywołaniem bloku, ale po utworzeniu bloku (wtedy wartość zostaje „przechwycona”).
DarkDust,
1
Jest to jednak pomocne wyjaśnienie - czy możesz wyjaśnić, co wydaje się być sprzecznymi stwierdzeniami w twoim wyjaśnieniu? Mówisz powyżej, że „aBlock modyfikuje ... localCounter”, a następnie w komentarzach mówisz „[aBlock] NIE modyfikuje localCounter”. Który to jest? Jeśli jest „niezmodyfikowany”, to czy należy edytować swoją odpowiedź?
Praxiteles
2
Ogólnie rzecz biorąc, zmienne bez __blocka byłyby wychwytywane według wartości i pakowane do „środowiska” bloku, gdy blok jest tworzony. Ale zmienne __block nie zostaną schwytane, ilekroć są używane wewnątrz bloku lub na zewnątrz bloku, są nazywane takimi, jakie są.
jchnxu
27

@bbum obejmuje bloki dogłębnie w poście na blogu i dotyczy typu pamięci __block.

__block to odrębny typ pamięci

Podobnie jak statyczny, automatyczny i niestabilny, __block jest rodzajem pamięci. Mówi kompilatorowi, że pamięcią zmiennych należy zarządzać inaczej.

...

Jednak w przypadku zmiennych __block blok nie jest zachowywany. Zachowanie i zwolnienie zależy od ciebie.
...

Jeśli chodzi o przypadki użycia, można __blockczasem znaleźć, aby uniknąć zachowania cykli, ponieważ nie zachowuje argumentu. Typowym przykładem jest użycie self.

//Now using myself inside a block will not 
//retain the value therefore breaking a
//possible retain cycle.
__block id myself = self;
Joe
źródło
Zobacz ten post, aby uzyskać więcej informacji na temat problemu z cyklem przechowywania: benscheirman.com/2012/01/… . Czy __weakwystarczyłoby również w tym konkretnym przypadku? Być może jest to trochę jaśniejsze ...
Hari Karam Singh,
17
Wreszcie twierdzenie, że __block może być użyte w celu uniknięcia silnych cykli odniesienia (aka zachowaj cykle) jest całkowicie błędne w kontekście ARC. Z uwagi na fakt, że w ARC __block powoduje, że zmienna jest silnie przywoływana, w rzeczywistości jest bardziej prawdopodobne, że je wywoła. stackoverflow.com/a/19228179/189006
Krishnan
10

Zwykle, gdy nie używasz __block, blok skopiuje (zachowa) zmienną, więc nawet jeśli zmodyfikujesz zmienną, blok ma dostęp do starego obiektu.

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

W tych 2 przypadkach potrzebujesz __block:

1. Jeśli chcesz zmodyfikować zmienną wewnątrz bloku i oczekiwać, że będzie widoczna na zewnątrz:

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

2. Jeśli chcesz zmodyfikować zmienną po zadeklarowaniu bloku i oczekujesz, że blok zobaczy zmianę:

__block NSString* str = @"hello";
void (^theBlock)() = ^void() {
    NSLog(@"%@", str);
};
str = @"how are you";
theBlock(); //prints "how are you"
Hamid Vakilian
źródło
8

__block to kwalifikator pamięci, z którego można korzystać na dwa sposoby:

  1. Oznacza, że ​​zmienna żyje w pamięci, która jest współużytkowana przez zakres leksykalny oryginalnej zmiennej i wszelkie bloki zadeklarowane w tym zakresie. I clang wygeneruje strukturę do reprezentowania tej zmiennej i użyje tej struktury przez odniesienie (a nie według wartości).

  2. W MRC można użyć __block, aby uniknąć zachowania zmiennych obiektowych, które przechwytuje blok. Uważaj, aby to nie zadziałało w przypadku ARC. W ARC powinieneś użyć __weak .

Szczegółowe informacje można znaleźć w dokumentacji Apple .

Mindy
źródło
6

__blockjest typem pamięci służącym do modyfikowania zmiennych zakresu, a szczerze mówiąc, jeśli zadeklarujesz zmienną za pomocą tego specyfikatora, to odniesienie zostanie przekazane do bloków, a nie tylko do odczytu, aby uzyskać więcej informacji, zobacz Programowanie bloków w iOS

mithilesh
źródło
2

mam nadzieję, że ci to pomoże

załóżmy, że mamy kod taki jak:

{
     int stackVariable = 1;

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

wyświetli błąd w rodzaju „zmiennej nie można przypisać”, ponieważ zmienna stosu wewnątrz bloku jest domyślnie niezmienna.

dodanie __block (modyfikator pamięci) przed deklaracją powoduje, że można go modyfikować wewnątrz bloku tj __block int stackVariable=1;

Anurag Bhakuni
źródło
1

Ze specyfikacji języka blokowego :

Oprócz nowego typu bloku wprowadzamy także nowy kwalifikator pamięci, __block, dla zmiennych lokalnych. [testme: deklaracja __block w dosłownym bloku] Kwalifikator pamięci __block wyklucza się wzajemnie z istniejącymi automatycznymi, rejestracyjnymi i statycznymi kwalifikatorami pamięci lokalnej. [testme] Zmienne kwalifikowane przez __block działają tak, jakby były w przydzielonej pamięci, a ta pamięć jest automatycznie odzyskuje się po ostatnim użyciu tej zmiennej. Implementacja może wybrać optymalizację, w której pamięć jest początkowo automatyczna i tylko „przenoszona” do przydzielonej pamięci (stosu) po Block_copy bloku odniesienia. Takie zmienne mogą być mutowane tak jak zmienne normalne.

W przypadku, gdy zmienna __block jest blokiem, należy założyć, że zmienna __block znajduje się w przydzielonej pamięci i jako taka przyjmuje się, że odwołuje się do bloku, który również znajduje się w przydzielonej pamięci (że jest wynikiem operacji Block_copy). Mimo to nie ma możliwości wykonywania Block_copy lub Block_release, jeśli implementacja zapewnia początkowe automatyczne przechowywanie bloków. Wynika to z nieodłącznego warunku wyścigu potencjalnie kilku wątków próbujących zaktualizować zmienną współdzieloną i potrzeby synchronizacji wokół pozbywania się starszych wartości i kopiowania nowych. Taka synchronizacja wykracza poza zakres tej specyfikacji języka.

Szczegółowe informacje na temat tego, do czego powinna się kompilować zmienna __block, patrz Specyfikacja implementacji bloku , sekcja 2.3.

Martin Gordon
źródło
Twoje linki są martwe
Ben Leggiero
To naprawdę nie jest odpowiedź i można ją rozwinąć lub usunąć. Dzięki!
Dan Rosenstark
0

Oznacza to, że zmienna, której jest prefiksem, jest dostępna do użycia w bloku.

Rich Allen
źródło