Frage Speichert dispatch_async interne Blöcke


Gegeben die folgenden (manuelle Referenzzählung):

void (^block)(void) = ^ {
    NSLog(@"wuttup");
}

void (^async_block)(void) = ^ {
    block();
}

dispatch_async(dispatch_get_main_queue(), async_block);

Wird "Blockieren" kopiert anstatt vom Stapel geworfen und zerstört?


5
2017-08-10 00:06


Ursprung


Antworten:


Ich glaube, die Antwort ist ja.

Der äußere Block wird asynchron ausgegeben, wodurch die Laufzeit eine Kopie auf dem Heap für diesen Block erstellt. Und wie unten gezeigt und beschrieben in der Block-Implementierungsspezifikation - Clang 3.4 DokumentationDie importierten Variablen des inneren Blocks werden ebenfalls in den Heap kopiert.

Im Beispiel des OP haben wir eine "importierte const Kopie einer Blockreferenz".

Ich verwende das Beispiel in der Spezifikation:

void (^existingBlock)(void) = ...;
void (^vv)(void) = ^{ existingBlock(); }
vv();

Die Spezifikation besagt, dass die copy_helper und dispose_helper Funktionen werden benötigt:

Die Funktion copy_helper wird sowohl dem vorhandenen Stack-basierten Zeiger als auch dem Zeiger auf die neue Heap-Version übergeben und sollte in die Runtime zurückrufen, um die Kopieroperation für die importierten Felder innerhalb des Blocks tatsächlich auszuführen.

Der folgende Beispielcode in der Spezifikation ist schwer zu entziffern (und tatsächlich fehlt die Beschreibung, was passiert, wenn der äußere Block in den Heap kopiert wird). Jedenfalls scheint die Spezifikation zu zeigen, dass importierte Variablen von inneren Blöcken (rekursiv) in den rohen Speicherbereich des äußeren Blocks kopiert werden.

Wenn der äußere Block auf den Heap kopiert wird, werden importierte Variablen der inneren Blöcke eventuell auch auf dem Heap gespeichert.

Nun, intuitiv macht das alles Sinn.

Ich habe ein kleines Testprogramm gemacht, das folgendes zeigen soll: (Sie müssen die Demontage debuggen und untersuchen, um herauszufinden, was unter der Oberfläche passiert).

#import <Foundation/Foundation.h>


void foo(int param)
{
    int x0 = param;
    int x1 = param + 1;
    void (^existingBlock)(void) = ^{
        int y0 = x0;
        int y1 = x1;
        printf("&y0: %p\n", &y0);
        printf("&y1: %p\n", &y1);
        printf("&x0: %p\n", &x0);
        printf("&x1: %p\n", &x1);
    };

    void (^vv)(void) = ^{
        int y2 = x0;
        int y3 = x1;
        existingBlock();
        printf("&y2: %p\n", &y2);
        printf("&y3: %p\n", &y3);
        printf("&x0: %p\n", &x0);
        printf("&x1: %p\n", &x1);
    };

    printf("Stack: &x: %p\n", &x0);
    printf("Stack: &x: %p\n", &x1);

    printf("------- on main thread -------\n");
    vv();

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        printf("------- on thread 2 -------\n");
        assert(vv);
        sleep(1);
        int y4 = x0;
        int y5 = x1;
        vv();
        printf("&y4: %p\n", &y4);
        printf("&y5: %p\n", &y5);
        printf("&x0: %p\n", &x0);
        printf("&x1: %p\n", &x1);
    });
}

int main(int argc, const char * argv[])
{
    @autoreleasepool {

        foo(1);
        sleep(2);
    }
    return 0;
}

Die Ausgabe ist wie folgt:

Stack: &x: 0x7fff5fbff868
Stack: &x: 0x7fff5fbff864
------- on main thread -------
&y0: 0x7fff5fbff70c
&y1: 0x7fff5fbff708
&x0: 0x1001081e0
&x1: 0x1001081e4
&y2: 0x7fff5fbff76c
&y3: 0x7fff5fbff768
&x0: 0x10010a588
&x1: 0x10010a58c
------- on thread 2 -------
&y0: 0x1000e5d9c
&y1: 0x1000e5d98
&x0: 0x1001081e0
&x1: 0x1001081e4
&y2: 0x1000e5dfc
&y3: 0x1000e5df8
&x0: 0x10010a588
&x1: 0x10010a58c
&y4: 0x1000e5e6c
&y5: 0x1000e5e68
&x0: 0x10010a5e8
&x1: 0x10010a5ec

Wenn der Block im Hauptthread ausgeführt wird, befindet er sich im Stapel (wie die Adressen der lokalen und importierten Variablen zeigen). Bei Ausführung über dispatch_async Die Laufzeit hat den Block kopiert - einschließlich der inneren Blöcke, wie aus den Adressen der lokalen und importierten Variablen der Blöcke ersichtlich ist.

Wir können einen Haltepunkt bei der copy_helper_block Funktion, und tatsächlich stoppt das Programm dort einmal, um den Block zu kopieren vv zum Haufen.

enter image description here


8
2017-08-10 08:41



Von dem Apple-Dokumente auf dispatch_async :

Block

The block to submit to the target dispatch queue. This function performs Block_copy and Block_release on behalf of callers. This parameter cannot be NULL.

Damit, async_block wird kopiert.

Pro diese Diskussion , block (Innen async_block in Ihrem Beispiel), wird ein readonly copy innerhalb async_block


5
2017-08-10 01:16



block ist retained (nicht kopiert per sé), um in einem anderen Block gefangen zu werden async_block genau wie gewöhnliche Objekte. Das Kopieren des Blocks ist das Ergebnis des gesendeten Blockobjekts a [block retain] Nachricht, die ein überschriebenes aufgerufen hat retain Methode, die den Block kopiert hat.


0
2017-08-10 01:15