Pregunta

Me empezó a usar bloques mucho y pronto se dio cuenta de que los bloques nil fallos en el bus:

typedef void (^SimpleBlock)(void);
SimpleBlock aBlock = nil;
aBlock(); // bus error

Esto parece ir en contra del comportamiento habitual de Objective-C que los mensajes Ignora a los objetos nulos:

NSArray *foo = nil;
NSLog(@"%i", [foo count]); // runs fine

Por lo tanto tengo que recurrir a la comprobación nula habitual antes de que yo utilizo un bloque:

if (aBlock != nil)
    aBlock();

o bloques uso ficticias:

aBlock = ^{};
aBlock(); // runs fine

¿Hay alguna otra opción? ¿Hay una razón por la cual los bloques nil no podían ser simplemente un nop?

¿Fue útil?

Solución

Me gustaría explicar esto un poco más, con una respuesta más completa. En primer lugar vamos a considerar este código:

#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {    
    void (^block)() = nil;
    block();
}

Si ejecuta esto, entonces usted va a ver un accidente en la línea block() que se ve algo como esto (cuando se ejecuta en una arquitectura de 32 bits - que es importante):

  

EXC_BAD_ACCESS (código = 2, dirección = 0xc)

Así que, ¿por qué? Pues bien, el 0xc es la parte más importante. Los medios de choque que el procesador ha tratado de leer la información en dirección de memoria 0xc. Esto es casi definitivamente una cosa totalmente incorrecta a hacer. Es poco probable que haya algo allí. Pero ¿por qué tratar de leer esta posición de memoria? Bueno, es debido a la forma en la que un bloque se construyó en realidad bajo el capó.

Cuando se define un bloque, el compilador en realidad crea una estructura en la pila, de esta forma:

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

El bloque es entonces un puntero a esta estructura. El cuarto miembro, invoke, de esta estructura es la que interesante. Es un puntero de función, señalando el código donde se lleva a cabo la ejecución del bloque. Por lo que los intentos de procesador para saltar a ese código cuando se invoca un bloque. Tenga en cuenta que si se cuenta el número de bytes en la estructura antes de que el miembro de invoke, encontrará que hay 12 en decimal, o C en hexadecimal.

Así que cuando se invoca un bloque, el procesador toma la dirección del bloque, agrega 12 e intenta cargar el valor se mantiene a esa dirección de memoria. A continuación, trata de saltar a esa dirección. Pero si el bloque es nula, entonces va a tratar de leer el 0xc dirección. Esta es una dirección sin valor, claramente, y así obtenemos el fallo de segmentación.

Ahora, la razón debe ser un accidente como éste en lugar de silencio no como una llamada de mensaje de Objective-C en verdad es realmente una opción de diseño. Dado que el compilador está haciendo el trabajo de decidir cómo invocar el bloque, que tendría que inyectar código de comprobación nula en todas partes se invoca un bloque. Esto aumentaría el tamaño del código y dar lugar a un mal desempeño. Otra opción sería utilizar una cama elástica, que realiza el chequeo de cero. Sin embargo, esto también incurren en penalización en el rendimiento. mensajes de Objective-C ya pasan por una cama elástica, ya que necesitan para buscar el método que realmente se invoca. El tiempo de ejecución permite la inyección lenta de métodos y cambiante de las implementaciones de métodos, por lo que ya está pasando por un trampolín de todos modos. La pena adicional de hacer la comprobación nula no es importante en este caso.

Espero que ayuda un poco para explicar la razón de ser.

Para obtener más información, vea mi blog de < a href = "http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-2/"> mensajes .

Otros consejos

La respuesta de Matt Galloway es perfecto! Gran leer!

Sólo quiero añadir que hay algunas maneras de hacer la vida más fácil. Se podría definir una macro como esta:

#define BLOCK_SAFE_RUN(block, ...) block ? block(__VA_ARGS__) : nil

Puede tomar 0 - n argumentos. Ejemplo de uso

typedef void (^SimpleBlock)(void);
SimpleBlock simpleNilBlock = nil;
SimpleBlock simpleLogBlock = ^{ NSLog(@"working"); };
BLOCK_SAFE_RUN(simpleNilBlock);
BLOCK_SAFE_RUN(simpleLogBlock);

typedef void (^BlockWithArguments)(BOOL arg1, NSString *arg2);
BlockWithArguments argumentsNilBlock = nil;
BlockWithArguments argumentsLogBlock = ^(BOOL arg1, NSString *arg2) { NSLog(@"%@", arg2); };
BLOCK_SAFE_RUN(argumentsNilBlock, YES, @"ok");
BLOCK_SAFE_RUN(argumentsLogBlock, YES, @"ok");

si usted desea conseguir en el valor de retorno del bloque y no está seguro de si existe el bloque o no, entonces usted está probablemente mejor simplemente escribiendo:

block ? block() : nil;

De esta manera se puede definir fácilmente el valor de retorno. En mi ejemplo las negativas.

Advertencia: No soy un experto en los bloques

.

Los bloques son Objective-C pero llamar a un bloque es no un mensaje , aunque aún se podría intentar [block retain]ing un bloque nil u otros mensajes.

Es de esperar que (y los enlaces) ayuda.

Esta es mi solución más bonito sencilla ... Tal vez no es posible escribir una función de ejecución universal con esas c var-args, pero no sé cómo escribir eso.

void run(void (^block)()) {
    if (block)block();
}

void runWith(void (^block)(id), id value) {
    if (block)block(value);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top