Як виконувати зворотні дзвінки в Objective-C


119

Як виконувати функції зворотного дзвінка в Objective-C?

Я просто хотів би побачити кілька завершених прикладів, і я повинен це зрозуміти.

Відповіді:


94

Зазвичай зворотні виклики в об'єктивному C здійснюються з делегатами. Ось приклад реалізації користувацького делегата;


Файл заголовка:

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

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

Файл реалізації (.m)

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

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

Це ілюструє загальний підхід. Ви створюєте категорію на NSObject, яка оголошує імена методів зворотного виклику. NSObject насправді не реалізує ці методи. Цей тип категорії називається неформальним протоколом, ви просто говорите, що багато методів можуть реалізувати ці методи. Вони є способом переслати оголошення про типовий підпис селектора.

Далі у вас є якийсь об'єкт - бути делегатом "MyClass", і MyClass викликає делегатні методи делегата, як це доречно. Якщо ваш зворотний зв'язок делегата не є обов’язковим, ви, як правило, захищаєте їх на сайті відправки з таким кшталт "if ([delegate respondsToSelector: @selector (myClassWillDoSomething :)) {". У моєму прикладі делегат зобов'язаний реалізувати обидва способи.

Замість неформального протоколу ви також можете використовувати офіційний протокол, визначений за допомогою @protocol. Якщо ви це зробите, ви змінили б тип делегатного сеттера та змінну екземпляра на " id <MyClassDelegate>", а не просто " id".

Також ви помітите, що делегат не зберігається. Зазвичай це робиться тому, що об'єкт, який "володіє" екземплярами "MyClass", як правило, також є делегатом. Якби MyClass зберігав свого делегата, тоді був би цикл збереження. Добре ідея методу dealloc класу, який має екземпляр MyClass і є його делегатом, щоб очистити цей посилання делегата, оскільки це слабкий зворотний покажчик. Інакше якщо щось підтримує екземпляр MyClass, ви матимете звисаючий покажчик.


+1 Добре ґрунтовна відповідь. Глазур на торті - це посилання на більш глибоку документацію Apple щодо делегатів. :-)
Квінн Тейлор

Джон, велике спасибі за вашу допомогу. Я дуже ціную вашу допомогу. Мені про це шкода, але я не надто зрозумілий у відповіді. Повідомлення .m - клас, який встановлюється як делегат під час виклику функції doSomething. Чи виконує функція зворотного дзвінка, яку викликає користувач? оскільки я відчуваю, що користувач закликає щось робити, а ваші функції зворотного дзвінка - це myClassWillDoSomethingg & myClassDidDoSomething. Також ви можете мені показати, як створити вищий клас, який викликає функцію зворотного дзвінка. Я програміст на C, тому я ще не надто знайомий із середовищем Obj-C.
ReachConnection

"Повідомлення .m" щойно означало, у вашому .m файлі. У вас був би окремий клас, назвемо його "Foo". Foo матиме змінну "MyClass * myClass", а в якийсь момент Foo сказав "[myClass setDelegate: self]". У будь-який момент після цього, якщо хто-небудь, включаючи foo, посилається на doSomethingMethod в цьому екземплярі MyClass, foo матиме свої методи myClassWillDoSomething та myClassDidDoSomething. Я фактично також опублікую другий інший приклад, в якому не використовуються делегати.
Джон Хесс

Я не думаю, що .m означає "повідомлення".
Чак


140

Для повноти, оскільки RSS StackOverflow просто випадковим чином відновив для мене питання, іншим (новішим) варіантом є використання блоків:

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

2
@Ahruman: Що означає "^" - символ у "void (^ _комплектHandler) (int someParameter);" означає? Чи можете ви пояснити, що робить цей рядок?
Konrad Höffner

2
Чи можете ви надати пояснення, чому потрібно скопіювати обробник зворотних дзвінків?
Шанс Елліот

52

Ось приклад, який виключає поняття делегатів і просто виконує необмежений зворотний дзвінок.

@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

Зазвичай метод - [Foo doSomethingAndNotifyObject: withSelector:] буде асинхронним, що зробить зворотний виклик кориснішим, ніж тут.


1
Велике спасибі, Джон. Я розумію вашу першу реалізацію зворотного дзвінка після ваших коментарів. Крім того, ваша друга реалізація зворотного дзвінка є більш простою. Обидва дуже хороші.
ReachConnection

1
Дякую за публікацію цього Джона, це було дуже корисно. Мені довелося змінити [object performSelectorwithObject: self]; to [object performSelector: selector withObject: self]; щоб змусити його правильно працювати.
Баньєр

16

Щоб це питання було актуальним, впровадження ARC iOS 5.0 означає, що це можна досягти за допомогою Blocks ще більш стисло:

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

Завжди F **** і синтаксис блоку, якщо ви коли-небудь забудете синтаксис блоку Objective-C.


У @interface повинен бути + (void)sayHi:(void(^)(NSString *reply))callback;НЕ+ (void)sayHi:(void(^)(NSString *))callback;
Tim007

Не по вищезазначеному F **** нг Блок Синтаксис : - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;(Примітка parameterTypesНЕ parameters)
Райан Броуді

4

CallBack: В Об'єкті C є 4 типи зворотних дзвінків

  1. Тип вибору : Ви можете побачити NSTimer, UIPangesture - приклади зворотного виклику Selector. Використовується для дуже обмеженого виконання коду.

  2. Тип делегата : поширений і найчастіше використовується в рамках Apple. UITableViewDelegate, NSNURLConnectionDelegate. Зазвичай вони використовуються для показу асинхронного завантаження багатьох зображень із сервера тощо.

  3. NSNotifications : NotificationCenter - одна з особливостей Цілі C, яка використовувалась для сповіщення багатьох одержувачів під час виникнення події.
  4. Блоки : Блоки частіше використовуються в програмуванні Objective C. Це відмінна функція і використовується для виконання фрагмента коду. Ви також можете направити підручник, щоб зрозуміти: Підручник з блоків

Будь ласка, дозвольте мені, якщо будь-які інші відповіді на це. Я оціню це.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.