Как выполнять обратные вызовы в Objective-C

119

Как выполнять функции обратного вызова в Objective-C?

Я просто хотел бы увидеть несколько завершенных примеров и должен это понять.

ReachConnection
источник

Ответы:

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 ([делегат responsedsToSelector: @selector (myClassWillDoSomething :)) {». В моем примере от делегата требуется реализовать оба метода.

Вместо неформального протокола вы также можете использовать формальный протокол, определенный с помощью @protocol. Если вы это сделаете, вы измените тип установщика делегата и переменную экземпляра на " id <MyClassDelegate>" вместо просто " id".

Кроме того, вы заметите, что делегат не сохраняется. Обычно это делается потому, что объект, который «владеет» экземплярами «MyClass», обычно также является делегатом. Если MyClass сохранит своего делегата, то будет цикл сохранения. В методе dealloc класса, который имеет экземпляр MyClass и является его делегатом, рекомендуется очистить ссылку на делегат, поскольку это слабый обратный указатель. В противном случае, если что-то поддерживает экземпляр MyClass, у вас будет висящий указатель.

Джон Хесс
источник
+1 Хороший обстоятельный ответ. Глазурь на торте была бы ссылкой на более подробную документацию Apple по делегатам. :-)
Куинн Тейлор
Джон, большое спасибо за вашу помощь. Я действительно ценю твою помощь. Я сожалею об этом, но я не совсем ясен в ответе. Message .m - это класс, который устанавливает себя в качестве делегата во время вызова функции doSomething. DoSomething - это функция обратного вызова, которую вызывает пользователь? поскольку у меня сложилось впечатление, что пользователь вызывает 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

Для полноты, поскольку StackOverflow RSS просто случайно воскресил для меня вопрос, другой (более новый) вариант - использовать блоки:

@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 (^ _completionHandler) (int someParameter);" значит? Не могли бы вы объяснить, что делает эта строчка?
Конрад Хёффнер
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 [объект performSelector: селектор withObject: self]; чтобы заставить его работать правильно.
Banjer
16

Чтобы поддерживать этот вопрос в актуальном состоянии, введение ARC в iOS 5.0 означает, что это может быть достигнуто с помощью блоков еще более кратко:

@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 **** ng Block, если вы когда-нибудь забудете синтаксис Objective-C.

Райан Броди
источник
В @interface должен быть + (void)sayHi:(void(^)(NSString *reply))callback;не+ (void)sayHi:(void(^)(NSString *))callback;
Tim007
Не в соответствии с вышеупомянутым синтаксисом F **** ng Block : - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;(Обратите внимание, что parameterTypesнет parameters)
Райан Броди
4

Обратный вызов: в Objective C есть 4 типа обратного вызова

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

  2. Тип делегата : общий и наиболее часто используемый в платформе Apple. Уитаблевиевделегате, NSNURLConnectionDelegate. Обычно они используются для демонстрации асинхронной загрузки множества изображений с сервера и т. Д.

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

Пожалуйста, позвольте мне ответить на этот вопрос. Я буду признателен.

Анил Гупта
источник