2011-06-21 6 views
12

que tienen un código que esencialmente se reduce a esto:¿Block_copy es recursivo?

-(void)doSomethingWithBlock:(BlockTypedef)block 
{ 
    [Foo doSomethingElseWithBlock:^() { 
     block(); 
    }]; 
} 

Foo doSomethingElseWithBlock: llamadas Block_copy y Block_release en el bloque que recibe. ¿Esto también es necesario en el alcance externo, o lo manejará el Block_copy interno?

+2

Quizás quiso probarlo? –

+0

Mi aplicación no es particularmente ejecutable en este punto, entonces no. Encontré esto: http://clang.llvm.org/docs/Block-ABI-Apple.txt que dice "Los bloques pueden contener expresiones literales de bloque. Todas las variables utilizadas dentro de los bloques internos se importan a todo el bloque que lo rodea. alcances incluso si las variables no se usan. Esto incluye las importaciones de const y las variables __block ". Sin embargo, en este caso es un literal de bloque que contiene un bloque, no el orden dado allí. –

Respuesta

8

cito la guía Blocks Programming Topics el sitio de documentación de desarrolladores de Apple:

Cuando se copia un bloque, cualquier referencia a otros bloques de dentro de ese bloque se copian si es necesario, un árbol entero puede ser copiado (del parte superior). Si tiene variables de bloque y hace referencia a un bloque dentro del bloque, ese bloque se copiará.

Cuando copia un bloque basado en pila, obtiene un nuevo bloque. Sin embargo, si copia un bloque basado en el montón, simplemente incrementa el conteo retenido de ese bloque y lo recupera como el valor devuelto de la función o el método de copia.

2

El interior Block_copy() no es realmente relevante aquí. Lo que desea hacer un seguimiento es si un bloque dado vive en la pila o en el montón. Considere este código en función de su ejemplo:

@interface Foo : NSObject 
@end 

@implementation Foo 

typedef void(^BlockTypedef)(void); 

+(void)doSomethingElseWithBlock:(BlockTypedef)block 
{ 
    NSLog(@"block=%@", block); 
    BlockTypedef myBlock = Block_copy(block); 
    NSLog(@"myBlock=%@", myBlock); 
    myBlock(); 
    Block_release(myBlock); 
} 

+(void)doSomethingWithBlock:(BlockTypedef)block 
{ 
    [Foo doSomethingElseWithBlock:^() { 
    block(); 
    }]; 
} 
@end 

int main (int argc, const char * argv[]) 
{ 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    int i = 3; 
    BlockTypedef block = ^{ printf("i=%d\n", i); }; 
    NSLog(@"block=%@", block); 
    [Foo doSomethingWithBlock:block]; 
    block(); 
    NSLog(@"block=%@", block); 
    [pool drain]; 
    return 0; 
} 

Esto debería estar bien, pero block y myblock son diferentes tipos de bloques. block es un bloque de pila y tiene el alcance de la pila de llamadas. Existirá hasta que salga main(). myblock es un bloque malloc (heap) y existirá hasta que se lance. Debe asegurarse de no intentar tomar una referencia no copiada al block y utilizarla después de que la pila esté lista. No puede pegar block en un ivar sin copiarlo.

Joachim Bengtsson tiene la mejor reseña de esto que yo sepa. @bbum también ha escrito al respecto. (Si bbum se acerca y dice que soy un idiota al respecto, escúchelo, pero creo que estoy aquí.)

3

Sí, esto es seguro. No necesita hacer una copia. En el momento en que -[Foo doSomethingElseWithBlock:] hace una copia de tu bloque literal, copiará el bloque interno al montón.

Escribí un código de prueba para comprobarme que esto sucede; vea cómo printer (usado solo en block1) se copia de la pila al montón en el momento en que se llama al Block_copy(block2).

#include <Block.h> 
#include <dispatch/dispatch.h> 
#include <stdio.h> 

typedef void (^void_block)(); 

class ScopedPrinter { 
    public: 
    ScopedPrinter() { 
     printf("construct %p\n", this); 
    } 
    ScopedPrinter(const ScopedPrinter& other) { 
     printf("copy %p <- %p\n", this, &other); 
    } 
    ~ScopedPrinter() { 
     printf("destroy %p\n", this); 
    } 
}; 

void_block invoke(void_block other) { 
    printf("other %p\n", (void*)other); 
    void_block block2 = ^{ 
     printf("other %p\n", (void*)other); 
     other(); 
    }; 
    printf("block2 created\n"); 
    block2 = Block_copy(block2); 
    printf("block2 copied\n"); 
    return block2; 
} 

void_block make_block() { 
    ScopedPrinter printer; 
    printf("printer created\n"); 
    void_block block1 = ^{ 
     printf("block1 %p\n", &printer); 
    }; 
    printf("block1 created\n"); 
    return invoke(block1); 
} 

int main() { 
    void_block block = make_block(); 
    block(); 
    Block_release(block); 
    return 0; 
} 

Transcripción:

construct 0x7fff6a23fa70 
printer created 
copy 0x7fff6a23fa50 <- 0x7fff6a23fa70 
block1 created 
other 0x7fff6a23fa30 
block2 created 
copy 0x10a700970 <- 0x7fff6a23fa50 
block2 copied 
destroy 0x7fff6a23fa50 
destroy 0x7fff6a23fa70 
other 0x10a700950 
block1 0x10a700970 
destroy 0x10a700970 
+0

Solo para agregar una cosa: incluso si 'make_block()' declaró 'block1' como' __block void_block block1' (por ejemplo, para que sea recursivo), aún estaría a salvo.'invoke()' no tiene que preocuparse por los especificadores de almacenamiento de sus calles. – sfiera