Objetivo-C pasar bloque como parámetro


Respuestas:


257

El tipo de un bloque varía según sus argumentos y su tipo de retorno. En el caso general, los tipos de bloque se declaran de la misma manera que los tipos de puntero de función, pero reemplazando el *con a ^. Una forma de pasar un bloque a un método es la siguiente:

- (void)iterateWidgets:(void (^)(id, int))iteratorBlock;

Pero como puedes ver, eso es desordenado. En su lugar, puede usar a typedefpara hacer que los tipos de bloques sean más limpios:

typedef void (^ IteratorBlock)(id, int);

Y luego pasa ese bloque a un método como este:

- (void)iterateWidgets:(IteratorBlock)iteratorBlock;

¿Por qué estás pasando id como argumento? ¿No es posible pasar fácilmente un NSNumber por ejemplo? ¿Cómo se vería eso?
bas

77
Por supuesto que puede pasar un argumento inflexible de tipos tales como NSNumber *o std::string&, o cualquier otra cosa que podría pasar como un argumento de función. Esto es solo un ejemplo. (Para un bloque que es equivalente a excepción de la sustitución idcon NSNumber, la typedefhabría typedef void (^ IteratorWithNumberBlock)(NSNumber *, int);.)
Jonathan Grynspan

Esto muestra la declaración del método. Un problema con los bloques es que el estilo de declaración "desordenado" no deja en claro y fácil escribir la llamada al método real con un argumento de bloque real.
uchuugaka

Typedefs no solo hace que el código sea más fácil de escribir, sino que también es mucho más fácil de leer ya que la sintaxis del puntero de bloque / función no es la más limpia.
pyj

@JonathanGrynspan, viniendo del mundo Swift pero teniendo que tocar algún código antiguo de Objective-C, ¿cómo puedo saber si un bloque se está escapando o no? Leí que, de forma predeterminada, los bloques escapan, excepto si están decorados NS_NOESCAPE, pero enumerateObjectsUsingBlockme dicen que no escapan, pero no veo NS_NOESCAPEningún lugar en el sitio, ni se menciona el escape en absoluto en los documentos de Apple. ¿Puede usted ayudar?
Mark A. Donohoe

62

La explicación más fácil para esta pregunta es seguir estas plantillas:

1. Bloquear como parámetro de método

Modelo

- (void)aMethodWithBlock:(returnType (^)(parameters))blockName {
        // your code
}

Ejemplo

-(void) saveWithCompletionBlock: (void (^)(NSArray *elements, NSError *error))completionBlock{
        // your code
}

Otro uso de casos:

2. Bloquear como una propiedad

Modelo

@property (nonatomic, copy) returnType (^blockName)(parameters);

Ejemplo

@property (nonatomic,copy)void (^completionBlock)(NSArray *array, NSError *error);

3. Bloquear como argumento de método

Modelo

[anObject aMethodWithBlock: ^returnType (parameters) {
    // your code
}];

Ejemplo

[self saveWithCompletionBlock:^(NSArray *array, NSError *error) {
    // your code
}];

4. Bloquear como una variable local

Modelo

returnType (^blockName)(parameters) = ^returnType(parameters) {
    // your code
};

Ejemplo

void (^completionBlock) (NSArray *array, NSError *error) = ^void(NSArray *array, NSError *error){
    // your code
};

5. Bloquear como typedef

Modelo

typedef returnType (^typeName)(parameters);

typeName blockName = ^(parameters) {
    // your code
}

Ejemplo

typedef void(^completionBlock)(NSArray *array, NSError *error);

completionBlock didComplete = ^(NSArray *array, NSError *error){
    // your code
};

1
[self saveWithCompletionBlock: ^ (matriz NSArray *, error NSError *) {// su código}]; En este ejemplo, ¿se ignora el tipo de retorno porque es nulo?
Alex

51

Esto puede ser útil:

- (void)someFunc:(void(^)(void))someBlock;

te falta un paréntesis
newacct

Este funcionó para mí mientras que el anterior no. Por cierto, amigo, eso fue realmente útil.
Tanou

23

Puede hacer esto, pasando el bloque como un parámetro de bloque:

//creating a block named "completion" that will take no arguments and will return void
void(^completion)() = ^() {
    NSLog(@"bbb");
};

//creating a block namd "block" that will take a block as argument and will return void
void(^block)(void(^completion)()) = ^(void(^completion)()) {
    NSLog(@"aaa");
    completion();
};

//invoking block "block" with block "completion" as argument
block(completion);

8

Una forma más de pasar bloque usando las funciones с en el ejemplo a continuación. He creado funciones para realizar cualquier cosa en segundo plano y en la cola principal.

archivo blocks.h

void performInBackground(void(^block)(void));
void performOnMainQueue(void(^block)(void));

archivo blocks.m

#import "blocks.h"

void performInBackground(void(^block)(void)) {
    if (nil == block) {
        return;
    }

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), block);
}

void performOnMainQueue(void(^block)(void)) {
    if (nil == block) {
        return;
    }

    dispatch_async(dispatch_get_main_queue(), block);
}

Luego importe import blocks.h cuando sea necesario e invoque:

- (void)loadInBackground {

    performInBackground(^{

        NSLog(@"Loading something in background");
        //loading code

        performOnMainQueue(^{
            //completion hadler code on main queue
        });
    });
}

6

También puede establecer el bloqueo como una propiedad simple si es aplicable para usted:

@property (nonatomic, copy) void (^didFinishEditingHandler)(float rating, NSString *reviewString);

¡asegúrese de que la propiedad de bloque sea "copiar"!

y, por supuesto, también puedes usar typedef:

typedef void (^SimpleBlock)(id);

@property (nonatomic, copy) SimpleBlock someActionHandler;



3

Escribí un Bloque de finalización para una clase que devolverá los valores de los dados después de que hayan sido sacudidos:

  1. Definir typedef con returnType ( declaración .hanterior @interface)

    typedef void (^CompleteDiceRolling)(NSInteger diceValue);
  2. Definir a @propertypara el bloque ( .h)

    @property (copy, nonatomic) CompleteDiceRolling completeDiceRolling;
  3. Definir un método con finishBlock( .h)

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock;
  4. Inserte el método definido anterior en el .marchivo y comprométase finishBlocka @propertydefinido antes

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock{
        self.completeDiceRolling = finishBlock;
    }
  5. Para desencadenar completionBlockpasar variableType predefinida (no olvide comprobar si completionBlockexiste)

    if( self.completeDiceRolling ){
        self.completeDiceRolling(self.dieValue);
    }

2

A pesar de las respuestas dadas en este hilo, realmente tuve problemas para escribir una función que tomara un Bloque como función, y con un parámetro. Finalmente, esta es la solución que se me ocurrió.

Quería escribir una función genérica loadJSONthread, que tomaría la URL de un servicio web JSON, cargaría algunos datos JSON de esta URL en un hilo de fondo y luego devolvería un NSArray * de resultados a la función de llamada.

Básicamente, quería mantener toda la complejidad del hilo de fondo oculta en una función genérica reutilizable.

Así es como llamaría a esta función:

NSString* WebServiceURL = @"http://www.inorthwind.com/Service1.svc/getAllCustomers";

[JSONHelper loadJSONthread:WebServiceURL onLoadedData:^(NSArray *results) {

    //  Finished loading the JSON data
    NSLog(@"Loaded %lu rows.", (unsigned long)results.count);

    //  Iterate through our array of Company records, and create/update the records in our SQLite database
    for (NSDictionary *oneCompany in results)
    {
        //  Do something with this Company record (eg store it in our SQLite database)
    }

} ];

... y este es el bit con el que luché: cómo declararlo y cómo hacer que llame a la función Bloquear una vez que se cargaron los datos, y pasar Blockun NSArray * de registros cargados:

+(void)loadJSONthread:(NSString*)urlString onLoadedData:(void (^)(NSArray*))onLoadedData
{
    __block NSArray* results = nil;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{

        // Call an external function to load the JSON data 
        NSDictionary * dictionary = [JSONHelper loadJSONDataFromURL:urlString];
        results = [dictionary objectForKey:@"Results"];

        dispatch_async(dispatch_get_main_queue(), ^{

            // This code gets run on the main thread when the JSON has loaded
            onLoadedData(results);

        });
    });
}

Esta pregunta de StackOverflow se refiere a cómo llamar a funciones, pasando un Bloque como parámetro, por lo que he simplificado el código anterior y no he incluido la loadJSONDataFromURLfunción.

Pero, si está interesado, puede encontrar una copia de esta función de carga JSON en este blog: http://mikesknowledgebase.azurewebsites.net/pages/Services/WebServices-Page6.htm

¡Espero que esto ayude a otros desarrolladores de XCode! (¡No olvide votar esta pregunta y mi respuesta, si es así!)


1
Este es realmente uno de los mejores trucos que he visto para ios y bloques. Me encanta hombre !!!!
portforwardpodcast

1

La plantilla completa se ve como

- (void) main {
    //Call
    [self someMethodWithSuccessBlock:^{[self successMethod];}
                    withFailureBlock:^(NSError * error) {[self failureMethod:error];}];
}

//Definition
- (void) someMethodWithSuccessBlock:(void (^) (void))successBlock
                   withFailureBlock:(void (^) (NSError*))failureBlock {

    //Execute a block
    successBlock();

//    failureBlock([[NSError alloc]init]);

}

- (void) successMethod {

}

- (void) failureMethod:(NSError*) error {

}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.