Bloco de aprovação de Objective-C como parâmetro

Respostas:

257

O tipo de um bloco varia de acordo com seus argumentos e seu tipo de retorno. No caso geral, os tipos de bloco são declarados da mesma maneira que os tipos de ponteiros de função, mas substituindo o *por a ^. Uma maneira de passar um bloco para um método é a seguinte:

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

Mas como você pode ver, isso é confuso. Você pode usar um typedefpara tornar os tipos de bloco mais limpos:

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

E então passe esse bloco para um método como este:

- (void)iterateWidgets:(IteratorBlock)iteratorBlock;
Jonathan Grynspan
fonte
Por que você está passando o id como argumento? Não é possível passar facilmente um NSNumber, por exemplo? Como isso seria?
bas
7
Você pode certamente passar um argumento de tipo forte como NSNumber *ou std::string&ou qualquer outra coisa que você poderia passar como argumento de função. Este é apenas um exemplo. (Para um bloco que é equivalente, exceto para a substituição idcom NSNumbero typedefseria typedef void (^ IteratorWithNumberBlock)(NSNumber *, int);.)
Jonathan Grynspan
Isso mostra a declaração do método. Um problema com os blocos é que o estilo de declaração "bagunçado" não torna claro e fácil escrever a chamada de método real com um argumento de bloco real.
uchuugaka
Os Typedefs não apenas facilitam a gravação do código, mas são significativamente mais fáceis de ler, pois a sintaxe do ponteiro de bloco / função não é a mais limpa.
Pyj # 18/14
@JonathanGrynspan, vindo do mundo Swift, mas tendo que tocar em algum código antigo do Objective-C, como posso saber se um bloco está escapando ou não? Eu li que, por padrão, os blocos estão escapando, exceto se decorados NS_NOESCAPE, mas enumerateObjectsUsingBlockme disseram que não é escapável, mas não vejo NS_NOESCAPEnenhum lugar no site, nem o escape mencionado nos documentos da Apple. Você pode ajudar?
Mark A. Donohoe 23/07
62

A explicação mais fácil para esta pergunta é seguir estes modelos:

1. Bloquear como parâmetro de método

Modelo

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

Exemplo

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

Outro uso de casos:

2. Bloquear como uma propriedade

Modelo

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

Exemplo

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

3. Bloquear como argumento de método

Modelo

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

Exemplo

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

4. Bloquear como uma variável local

Modelo

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

Exemplo

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

5. Bloquear como um typedef

Modelo

typedef returnType (^typeName)(parameters);

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

Exemplo

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

completionBlock didComplete = ^(NSArray *array, NSError *error){
    // your code
};
EnriMR
fonte
1
[self saveWithCompletionBlock: ^ (NSArray * array, NSError * erro) {// seu código}]; Neste exemplo, o tipo de retorno é ignorado porque é nulo?
Alex
51

Isso pode ser útil:

- (void)someFunc:(void(^)(void))someBlock;
quaertym
fonte
está faltando um parêntese
newacct 08/12/12
Este funcionou para mim, enquanto o anterior não. Btw obrigado companheiro, isso foi, de fato, útil!
tanou
23

Você pode fazer assim, passando o bloco como um parâmetro do bloco:

//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);
Aleksei Minaev
fonte
8

Mais uma maneira de passar o bloco usando as funções ñ no exemplo abaixo. Eu criei funções para executar qualquer coisa em segundo plano e na fila principal.

arquivo blocks.h

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

arquivo 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);
}

Em seguida, importe blocks.h quando necessário e chame-o:

- (void)loadInBackground {

    performInBackground(^{

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

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

Você também pode definir o bloco como uma propriedade simples, se aplicável:

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

verifique se a propriedade do bloco é "cópia"!

e é claro que você também pode usar o typedef:

typedef void (^SimpleBlock)(id);

@property (nonatomic, copy) SimpleBlock someActionHandler;
iiFreeman
fonte
4

Costumo sempre esquecer a sintaxe dos blocos. Isso sempre vem à minha mente quando preciso declarar um bloqueio. Eu espero que isso ajude alguém :)

http://fuckingblocksyntax.com

Juan Sagasti
fonte
Isso economizou meu tempo
Le Ding
3

Escrevi um completeBlock para uma classe que retornará os valores dos dados depois que eles foram sacudidos:

  1. Defina typedef com returnType ( declaração .hacima @interface)

    typedef void (^CompleteDiceRolling)(NSInteger diceValue);
  2. Defina a @propertypara o bloco ( .h)

    @property (copy, nonatomic) CompleteDiceRolling completeDiceRolling;
  3. Defina um método com finishBlock( .h)

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock;
  4. Insira o método definido anterior no .marquivo e confirme finishBlockcom o @propertydefinido antes

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock{
        self.completeDiceRolling = finishBlock;
    }
  5. Para acionar a completionBlockpassagem de typeType predefinido (não se esqueça de verificar se completionBlockexiste)

    if( self.completeDiceRolling ){
        self.completeDiceRolling(self.dieValue);
    }
Alex Cio
fonte
2

Apesar das respostas dadas neste tópico, eu realmente lutei para escrever uma função que levaria um bloco como uma função - e com um parâmetro. Eventualmente, aqui está a solução que eu encontrei.

Eu queria escrever uma função genérica loadJSONthread, que pegasse a URL de um serviço da Web JSON, carregasse alguns dados JSON dessa URL em um encadeamento em segundo plano e retornasse um NSArray * dos resultados de volta à função de chamada.

Basicamente, eu queria manter toda a complexidade do thread em segundo plano escondida em uma função reutilizável genérica.

Aqui está como eu chamaria essa função:

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

} ];

... e foi com isso que lutei: como declará-lo e como fazê-lo chamar a função Block depois que os dados foram carregados e passar o BlockNSArray * dos registros carregados:

+(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 questão StackOverflow se refere a como chamar funções, passando um bloco como parâmetro, então simplifiquei o código acima e não incluí a loadJSONDataFromURLfunção.

Mas, se você estiver interessado, poderá encontrar uma cópia dessa função de carregamento JSON neste blog: http://mikesknowledgebase.azurewebsites.net/pages/Services/WebServices-Page6.htm

Espero que isso ajude outros desenvolvedores de XCode! (Não esqueça de votar nesta pergunta e na minha resposta, se houver!)

Mike Gledhill
fonte
1
Este é seriamente um dos melhores truques que já vi para ios e blocos. Amor cara !!!!
Portforwardpodcast 14/03/16
1

O modelo completo parece

- (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 {

}
yoAlex5
fonte