Jak wykonywać wywołania zwrotne w Objective-C

119

Jak wykonywać funkcje oddzwaniania w Objective-C?

Chciałbym tylko zobaczyć kilka ukończonych przykładów i powinienem to zrozumieć.

ReachConnection
źródło

Odpowiedzi:

94

Zwykle wywołania zwrotne w celu C są wykonywane z delegatami. Oto przykład niestandardowej implementacji delegata;


Plik nagłówkowy:

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

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

Plik implementacji (.m)

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

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

To ilustruje ogólne podejście. Tworzysz kategorię na NSObject, która deklaruje nazwy twoich metod wywołania zwrotnego. NSObject w rzeczywistości nie implementuje tych metod. Ten typ kategorii nazywa się protokołem nieformalnym, po prostu mówisz, że wiele obiektów może implementować te metody. Są sposobem na przekazanie dalej deklaracji typu selektora.

Następnie masz jakiś obiekt będący delegatem „MyClass” i MyClass wywołuje metody delegata na delegacie, jeśli jest to wymagane. Jeśli wywołania zwrotne Twojego delegata są opcjonalne, zazwyczaj będziesz ich strzegł w miejscu wysyłki za pomocą czegoś takiego jak „if ([delegate respondsToSelector: @selector (myClassWillDoSomething :)) {". W moim przykładzie delegat musi zaimplementować obie metody.

Zamiast nieformalnego protokołu można również użyć formalnego protokołu zdefiniowanego za pomocą @protocol. Jeśli to zrobisz, zmienisz typ metody ustawiającej delegata i zmiennej instancji na „ id <MyClassDelegate>” zamiast tylko „ id”.

Zauważysz również, że delegat nie jest zatrzymany. Dzieje się tak zazwyczaj, ponieważ obiekt, który jest „właścicielem” wystąpień „MyClass”, jest zazwyczaj również delegatem. Gdyby MyClass zachował swojego delegata, wystąpiłby cykl przechowywania. W metodzie dealloc klasy, która ma instancję MyClass i jest jej delegatem, dobrym pomysłem jest wyczyszczenie odwołania do delegata, ponieważ jest to słaby wskaźnik wsteczny. W przeciwnym razie, jeśli coś utrzymuje instancję MyClass przy życiu, będziesz mieć wiszący wskaźnik.

Jon Hess
źródło
+1 Dobra, dokładna odpowiedź. Lukier na torcie byłby łączem do bardziej szczegółowej dokumentacji Apple na temat delegatów. :-)
Quinn Taylor
Jon, bardzo dziękuję za pomoc. Doceniam twoją pomoc. Przykro mi z tego powodu, ale nie mam jasności co do odpowiedzi. Message .m to klasa, która ustawia się jako delegat podczas wywołania funkcji doSomething. Czy doSomething jest funkcją wywołania zwrotnego, którą wywołuje użytkownik? ponieważ mam wrażenie, że użytkownik wywołuje doSomething, a funkcje wywołania zwrotnego to myClassWillDoSomethingg i myClassDidDoSomething. Czy możesz mi również pokazać, jak utworzyć wyższą klasę, która wywołuje funkcję oddzwaniania. Jestem programistą C, więc nie znam jeszcze środowiska Obj-C.
ReachConnection
„Wiadomość .m” oznaczała tylko, że jesteś w pliku .m. Miałbyś osobną klasę, nazwijmy ją „Foo”. Foo miałoby zmienną „MyClass * myClass”, aw pewnym momencie Foo powiedziałoby „[myClass setDelegate: self]”. W dowolnym momencie, jeśli ktokolwiek, w tym foo, wywołałby metodę doSomethingMethod w tym wystąpieniu MyClass, foo wywołałoby metody myClassWillDoSomething i myClassDidDoSomething. W rzeczywistości opublikuję również drugi inny przykład, który nie używa delegatów.
Jon Hess
Nie sądzę, by .m oznaczało „wiadomość”.
Chuck
140

Dla kompletności, ponieważ StackOverflow RSS po prostu losowo wskrzesił pytanie dla mnie, drugą (nowszą) opcją jest użycie bloków:

@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
źródło
2
@Ahruman: Co oznacza znak „^” - w „void (^ _completionHandler) (int someParameter);” oznaczać? Czy mógłbyś wyjaśnić, co robi ta linia?
Konrad Höffner
2
Czy możesz wyjaśnić, dlaczego musisz skopiować program obsługi wywołań zwrotnych?
Elliot Chance,
52

Oto przykład, który usuwa koncepcje delegatów i po prostu wykonuje nieprzetworzone wywołanie zwrotne.

@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

Zazwyczaj metoda - [Foo doSomethingAndNotifyObject: withSelector:] byłaby asynchroniczna, co uczyniłoby wywołanie zwrotne bardziej użytecznym niż tutaj.

Jon Hess
źródło
1
Wielkie dzięki, John. Rozumiem Twoją pierwszą implementację wywołania zwrotnego po komentarzach. Poza tym druga implementacja wywołania zwrotnego jest prostsza. Obie są bardzo dobre.
ReachConnection
1
Dzięki za opublikowanie tego Jon, było to bardzo pomocne. Musiałem zmienić [obiekt performSelectorwithObject: self]; to [obiekt performSelector: selector withObject: self]; aby działał poprawnie.
Banjer
16

Aby to pytanie było aktualne, wprowadzenie ARC w iOS 5.0 oznacza, że ​​można to osiągnąć za pomocą Blocks jeszcze bardziej zwięźle:

@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);
}];

Zawsze jest F **** ng Block Składnia, jeśli kiedykolwiek zapomnisz składni Block Objective-C.

Ryan Brodie
źródło
W @interface powinna być + (void)sayHi:(void(^)(NSString *reply))callback;nie+ (void)sayHi:(void(^)(NSString *))callback;
Tim007
Nie zgodnie z wyżej wymienioną składnią bloku F **** ng : - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;(Uwaga parameterTypesnie parameters)
Ryan Brodie
4

CallBack: istnieją 4 typy wywołań zwrotnych w celu C

  1. Typ selektora : możesz zobaczyć NSTimer, UIPangesture to przykłady wywołania zwrotnego selektora. Używany do bardzo ograniczonego wykonywania kodu.

  2. Typ delegata : typowy i najczęściej używany w środowisku Apple. UITableViewDelegate, NSNURLConnectionDelegate. Zwykle są używane do wyświetlania asynchronicznego pobierania wielu obrazów z serwera itp.

  3. NSNotifications : NotificationCenter jest jedną z funkcji celu C, która służyła do powiadamiania wielu odbiorców w momencie wystąpienia zdarzenia.
  4. Bloki : Bloki są częściej używane w programowaniu Objective C. Jest to świetna funkcja i służy do wykonywania fragmentów kodu. Możesz również skorzystać z samouczka, aby zrozumieć: Samouczek dotyczący bloków

Proszę, pozwól mi odpowiedzieć na to pytanie. Docenię to.

Anil Gupta
źródło