Como executar retornos de chamada no Objective-C

119

Como executar funções de retorno de chamada no Objective-C?

Gostaria apenas de ver alguns exemplos completos e devo entender.

ReachConnection
fonte

Respostas:

94

Normalmente, retornos de chamada no objetivo C são feitos com delegados. Aqui está um exemplo de uma implementação delegada personalizada;


Arquivo de cabeçalho:

@interface MyClass : NSObject {
    id delegate;
}
- (void)setDelegate:(id)delegate;
- (void)doSomething;
@end

@interface NSObject(MyDelegateMethods)
- (void)myClassWillDoSomething:(MyClass *)myClass;
- (void)myClassDidDoSomething:(MyClass *)myClass;
@end

Arquivo de implementação (.m)

@implementation MyClass
- (void)setDelegate:(id)aDelegate {
    delegate = aDelegate; /// Not retained
}

- (void)doSomething {
    [delegate myClassWillDoSomething:self];
    /* DO SOMETHING */
    [delegate myClassDidDoSomething:self];
}
@end

Isso ilustra a abordagem geral. Você cria uma categoria no NSObject que declara os nomes dos seus métodos de retorno de chamada. NSObject, na verdade, não implementa esses métodos. Esse tipo de categoria é chamado de protocolo informal, você está apenas dizendo que muitos objetos podem implementar esses métodos. Eles são uma maneira de encaminhar a declaração de tipo do seletor.

Em seguida, você tem algum objeto como o delegado de "MyClass" e MyClass chama os métodos de delegado no delegado, conforme apropriado. Se seus retornos de chamada do delegado forem opcionais, você normalmente os protegerá no site de envio com algo como "if ([delegate respondesToSelector: @selector (myClassWillDoSomething :)) {". No meu exemplo, o delegado é necessário para implementar os dois métodos.

Em vez de um protocolo informal, você também pode usar um protocolo formal definido com @protocol. Se você fizer isso, altere o tipo do configurador de delegado e a variável de instância para " id <MyClassDelegate>" em vez de apenas " id".

Além disso, você notará que o delegado não é mantido. Isso geralmente é feito porque o objeto que "possui" instâncias de "MyClass" também é normalmente o delegado. Se o MyClass mantivesse seu delegado, haveria um ciclo de retenção. É uma boa idéia no método desalocado de uma classe que tenha uma instância MyClass e é seu representante para limpar essa referência de delegado, pois é um ponteiro fraco para trás. Caso contrário, se algo estiver mantendo viva a instância MyClass, você terá um ponteiro pendente.

Jon Hess
fonte
+1 Boa resposta completa. A cereja no topo do bolo seria um link para uma documentação mais aprofundada da Apple sobre os delegados. :-)
Quinn Taylor
Jon, muito obrigado pela sua ajuda. Eu realmente aprecio sua ajuda. Sinto muito por isso, mas não sou muito claro sobre a resposta. A mensagem .m é uma classe que se define como delegada durante a chamada de função doSomething. DoSomething é a função de retorno de chamada que o usuário chama? pois tenho a impressão de que o usuário chama doSomething e suas funções de retorno de chamada são myClassWillDoSomethingg & myClassDidDoSomething. Além disso, você pode me mostrar como criar uma classe superior que chama a função de retorno de chamada. Como sou programador em C, ainda não estou muito familiarizado com o ambiente Obj-C.
ReachConnection 19/06/2009
"Mensagem .m" significa apenas no seu arquivo .m. Você teria uma aula separada, vamos chamá-la de "Foo". O Foo teria uma variável "MyClass * myClass" e, em algum momento, o Foo diria "[myClass setDelegate: self]". A qualquer momento, se alguém, incluindo foo, invocasse o doSomethingMethod nessa instância do MyClass, foo teria seus métodos myClassWillDoSomething e myClassDidDoSomething invocados. Na verdade, também vou postar um segundo exemplo diferente que não usa delegados.
21719 Jon Hess
Eu não acho que o .m significa "mensagem".
21330 Chuck
140

Para ser completo, como o StackOverflow RSS apenas ressuscitou aleatoriamente a pergunta para mim, a outra opção (mais recente) é usar blocos:

@interface MyClass: NSObject
{
    void (^_completionHandler)(int someParameter);
}

- (void) doSomethingWithCompletionHandler:(void(^)(int))handler;
@end


@implementation MyClass

- (void) doSomethingWithCompletionHandler:(void(^)(int))handler
{
    // NOTE: copying is very important if you'll call the callback asynchronously,
    // even with garbage collection!
    _completionHandler = [handler copy];

    // Do stuff, possibly asynchronously...
    int result = 5 + 3;

    // Call completion handler.
    _completionHandler(result);

    // Clean up.
    [_completionHandler release];
    _completionHandler = nil;
}

@end

...

MyClass *foo = [[MyClass alloc] init];
int x = 2;
[foo doSomethingWithCompletionHandler:^(int result){
    // Prints 10
    NSLog(@"%i", x + result);
}];
Jens Ayton
fonte
2
@Ahruman: O que o caractere "^" em "void (^ _completionHandler) (int someParameter);" significar? Você poderia explicar o que essa linha faz?
Konrad Höffner
2
Você pode fornecer uma explicação de por que você precisa copiar o manipulador de retorno de chamada?
Elliot Possibilidade
52

Aqui está um exemplo que mantém os conceitos de delegados afastados e faz apenas uma chamada bruta de volta.

@interface Foo : NSObject {
}
- (void)doSomethingAndNotifyObject:(id)object withSelector:(SEL)selector;
@end

@interface Bar : NSObject {
}
@end

@implementation Foo
- (void)doSomethingAndNotifyObject:(id)object withSelector:(SEL)selector {
    /* do lots of stuff */
    [object performSelector:selector withObject:self];
}
@end

@implementation Bar
- (void)aMethod {
    Foo *foo = [[[Foo alloc] init] autorelease];
    [foo doSomethingAndNotifyObject:self withSelector:@selector(fooIsDone:)];
}

- (void)fooIsDone:(id)sender {
    NSLog(@"Foo Is Done!");
}
@end

Normalmente, o método - [Foo doSomethingAndNotifyObject: withSelector:] seria assíncrono, o que tornaria o retorno de chamada mais útil do que aqui.

Jon Hess
fonte
1
Muito obrigado John. Entendo sua primeira implementação de retorno de chamada após seus comentários. Além disso, sua segunda implementação de retorno de chamada é mais direta. Ambos são muito bons.
ReachConnection
1
Obrigado por postar este Jon, foi muito útil. Eu tive que mudar [objeto performSelectorwithObject: self]; para [objeto performSelector: seletor comObject: self]; para que funcione corretamente.
Banjer 12/10/10
16

Para manter essa pergunta atualizada, a introdução do ARC no iOS 5.0 significa que isso pode ser alcançado usando Blocos de maneira ainda mais concisa:

@interface Robot: NSObject
+ (void)sayHi:(void(^)(NSString *))callback;
@end

@implementation Robot
+ (void)sayHi:(void(^)(NSString *))callback {
    // Return a message to the callback
    callback(@"Hello to you too!");
}
@end

[Robot sayHi:^(NSString *reply){
  NSLog(@"%@", reply);
}];

Sempre existe a sintaxe do bloco, se você esquecer a sintaxe do bloco do Objective-C.

Ryan Brodie
fonte
Na interface @ + (void)sayHi:(void(^)(NSString *reply))callback;não deve ser #+ (void)sayHi:(void(^)(NSString *))callback;
0000 Tim007 31/03
Não de acordo com referido F **** ng Bloco Sintaxe : - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;(Nota parameterTypesnão parameters)
Ryan Brodie
4

CallBack: Existem 4 tipos de retorno de chamada no Objetivo C

  1. Tipo de seletor : você pode ver NSTimer, UIPangesture e os exemplos de retorno de chamada do seletor. Usado para execução muito limitada de código.

  2. Tipo de Delegado : Comum e mais usado na estrutura da Apple. UITableViewDelegate, NSNURLConnectionDelegate. Eles geralmente são usados ​​para mostrar o download de muitas imagens do servidor de forma assíncrona etc.

  3. NSNotifications : NotificationCenter é um dos recursos do Objective C que costumava notificar muitos destinatários no momento em que o evento ocorre.
  4. Blocos : os blocos são mais comumente usados ​​na programação do Objective C. É um ótimo recurso e usado para executar um pedaço de código. Você também pode consultar o tutorial para entender: Tutorial de blocos

Por favor, deixe-me se houver outra resposta para isso. Eu aprecio isso.

Anil Gupta
fonte