debug is on the table: @selector, Blocks e NSInvocation
O iOS trabalha fortemente com o padrão Delegate, onde você determina uma classe que implementa um dado protocolo e a mesma tratará algum tipo de evento. Isso nada mais é do que o padrão de projeto observer (levemente modificado). Mas em diversos pontos da API também vemos uma outra abordagem: as funções/metódos de callback. Os callbacks, bem famosos no JavaScript, são métodos ou funções que são passados como parâmetros para outros métodos, e são chamados em um dado momento da execução do método para qual ele foi passado.
Vou falar nesse post de como podemos usar os callbacks no iOS.
Uma forma de usar os callbacks é usando selectors. Digamos que você esteja implementando um método que fará uma série de processamento, e ao final você quer avisar para quem chamou esse método que o mesmo acabou. Isso seria a clássica implementação de um Observer (ou Delegate). Mas podemos fazer com selectors dessa forma:
// Precisamos pra chamar objc_msgSend
- (void) executar:(SEL)funcaoDeCallback {
NSLog(@"Um monte de processamento aqui…");
if ([self respondsToSelector:funcaoDeCallback]){
objc_msgSend(self, funcaoDeCallback, @"Glauber");
O método executar tem um parâmetro do tipo SEL, que é um ponteiro para um método. Dentro do método checamos se a nossa classe (self) responderá aquele selector, ou seja, se ela declarou um método com essa assinatura. Em caso positivo, usamos a função objc_msgSend passando qual o objeto que tem o método, o selector (ou seja, o método) e um parâmetro (que eu quis passar só pra ilustrar). Então, para executarmos um selector, temos sempre que passar o target (que é o objeto que tem o método) e o selector.
Aqui nós usamos a função objc_msgSend ao invés do famoso performSelector. Isso porque com a migração para o ARC, o compilador não consegue garantir que a função existe e então exibe um Warning. Uma alternativa para essa função e continuar usando o performSelector é conforme abaixo:
[self performSelector:funcaoDeCallback
withObject:@"Glauber"];
E para chamar o método executar, de modo que depois ele chame o método aqui:
SEL selector = @selector(meuMetodo:);
[self executar:selector];
Digamos que o método aqui ficasse dessa forma:
- (NSString *)meuMetodo:(NSString *)texto {
NSLog(@"O executar retornou: %@", texto);
Um exemplo clássico do uso de selector na API é na criação de botões para NavigationBar, onde determinamos o método que será chamado quando clicar no mesmo.
UIBarButtonItem *leftButton = [[UIBarButtonItem alloc]
initWithTitle:@"Esquerda"
target:self action:@selector(meuClick)];
NSIvocation
E como faríamos para pegar o retorno usando @selector? Temos uma outra alternativa. Utilizar a classe NSInvocation.
SEL selector = @selector(aqui:);
NSString *parametro = @"Glauber";
NSMethodSignature * assinatura =
[self methodSignatureForSelector:selector];
NSInvocation * invocacao = [NSInvocation
invocationWithMethodSignature:assinatura];
[invocacao setTarget:self];
[invocacao setSelector:selector];
[invocacao setArgument:&parametro atIndex:2];
[invocacao invoke];
NSString *retorno;
[invocacao getReturnValue:&retorno];
NSLog(@"Retorno: %@", retorno);
Notem que para passar o parâmetro usamos 2 para o primeiro (e único) parâmetro. Isso porque as posições 0 e 1 estão reservadas para self e _cmd respectivamente.
A partir do iOS 4, foi disponibilizado uma outra forma de usar funções de callback, os blocks. Seu propósito é bem similar ao selector. Eles são particularmente úteis para executar ações concorrentes ou para callbacks. As principais vantagens dos blocks são: Eles permitem escrever código o código perto da sua chamada (como inner classes do Java); e permitem acessar variáveis locais. Vejamos abaixo a sintaxe e como ele funciona.
int multiplicador = 7;
int (^meuBloco)(int) = ^(int num) {
return num * multiplicador;
NSLog("%d", meuBloco(3));
No código acima, declaramos um bloco que retorna um int, chama-se meuBloco e recebe um inteiro como parâmetro. O nome do parâmetro (num) é definido na inicialização do block. Se quisermos declarar um método que recebe um block que retorna void e recebe uma NSString por exemplo:
- (void)meuMetodoRecebeUmBlock:(void (^)(NSString *texto))bloco{
NSLog(@"Faça alguma coisa");
bloco(@"Terminou");
E para chamar:
[self meuMetodoRecebeUmBlock:^(NSString* texto){
NSLog(@"meuMetodoRecebeUmBlock disse: %@", texto);
Outra grande vantagem de utilizar os blocks em relação ao selector, é que não se faz necessário passar o target para o método. Podemos chamá-lo diretamente sem ter a referência do objeto que tem o método. Além disso, conseguimos pegar um retorno de um método mais facilmente do que com o @selector.