Подтвердить что ты не робот

Функция делегата и функция обратного вызова

Я работаю на платформе iOS, я хочу знать, что такое функция делегата и что такое функция обратного вызова? в чем разница между двумя типами функций или они одинаковы?

примером функции делегата является numberOfRowsInSection в UITableViewDelegate, а пример функции обратного вызова didReceiveLocalNotification в appDelegate.m

Можем ли мы создать собственную функцию обратного вызова в Objective-C, если ДА, дайте пример...

Спасибо..

4b9b3361

Ответ 1

Несколько мыслей:

  • Вы предполагаете, что didReceiveLocationNotification была "функцией обратного вызова", но на самом деле это всего лишь метод делегата протокола UIApplicationDelegate. Таким образом, оба numberOfRowsInSection и didReceiveLocalNotification - это просто методы делегирования.

    Что-то более похожее на общую функцию обратного вызова будет selector при планировании a NSTimer или определении обработчика для UIGestureRecognizer, где выбор имени метода не задан.

    Или обратные вызовы широко используются в CFArray.

  • Но корень вашего вопроса меньше терминологии, а скорее вопрос о том, как определить интерфейс, в котором вызывающий может указать метод, который какой-либо другой объект будет вызывать (асинхронно) в какой-то будущей дате. Существует несколько общих шаблонов:

    • Блокировать параметр для метода: все чаще используется метод, определяющий блок как параметр. Например, у вас может быть метод, который определяется следующим образом:

      - (NSURLSessionTask *)downloadAsynchronously:(NSURL *)url filename:(NSString *)filename completion:(void (^)(NSData *results, NSString *filename))completion {
          NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
              dispatch_async(dispatch_get_main_queue(), ^{
                  completion(data, filename);
              });
          }];
          [task resume];
      
          return task;
      }
      

      Этот третий параметр completion - это блок кода, который будет вызываться с загрузкой. Таким образом, вы можете вызвать этот метод следующим образом:

      [self downloadAsynchronously:url filename:filename completion:^(NSData *results, NSString *filename) {
          NSLog(@"Downloaded %d bytes", [results length]);
          [results writeToFile:filename atomically:YES];
      }];
      
      NSLog(@"%s done", __FUNCTION__);
      

      Вы увидите, что сообщение "done" появится немедленно и что при загрузке будет вызываться блок completion. По общему признанию, требуется некоторое время, чтобы привыкнуть к неуклюжему беспорядку пунктуации, который представляет собой определение блочной переменной/параметра, но как только вы знакомы с синтаксисом блока, вы действительно оцените этот шаблон. Это устраняет разрыв между вызовом какого-либо метода и определением некоторой отдельной функции обратного вызова.

      Если вы хотите упростить синтаксис работы с блоками в качестве параметров, вы можете фактически определить typedef для вашего блока завершения:

      typedef void (^DownloadCompletionBlock)(NSData *results, NSString *filename);
      

      И тогда сама декларация метода упрощается:

      - (NSURLSessionTask *)downloadAsynchronously:(NSURL *)url filename:(NSString *)filename completion:(DownloadCompletionBlock)completion {
          NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
              dispatch_async(dispatch_get_main_queue(), ^{
                  completion(data, filename);
              });
          }];
          [task resume];
      
          return task;
      } 
      
    • Шаблон делегирования-протокола. Другим распространенным методом передачи данных между объектами является шаблон протокола делегата. Сначала вы определяете протокол (характер интерфейса обратного вызова):

      @protocol DownloadDelegate <NSObject>
      
      - (NSURLSessionTask *)didFinishedDownload:(NSData *)data filename:(NSString *)filename;
      
      @end
      

      Затем вы определяете свой класс, который будет вызывать этот метод DownloadDelegate:

      @interface Downloader : NSObject
      
      @property (nonatomic, weak) id<DownloadDelegate> delegate;
      
      - (instancetype)initWithDelegate:(id<DownloadDelegate>)delegate;
      - (NSURLSessionTask *)downloadAsynchronously:(NSURL *)url filename:(NSString *)filename;
      
      @end
      
      @implementation Downloader
      
      - (instancetype)initWithDelegate:(id<DownloadDelegate>)delegate {
          self = [super init];
          if (self) {
              _delegate = delegate;
          }
          return self;
      }
      
      - (NSURLSessionTask *)downloadAsynchronously:(NSURL *)url filename:(NSString *)filename {
          NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
              dispatch_async(dispatch_get_main_queue(), ^{
                  [self.delegate didFinishedDownload:data filename:filename];
              });
          }];
          [task resume];
      
          return task;
      }
      
      @end
      

      И, наконец, исходный контроллер представления, который использует этот новый класс Downloader, должен соответствовать протоколу DownloadDelegate:

      @interface ViewController () <DownloadDelegate>
      
      @end
      

      И определите метод протокола:

      - (void)didFinishedDownload:(NSData *)data filename:(NSString *)filename {
          NSLog(@"Downloaded %d bytes", [data length]);
          [data writeToFile:filename atomically:YES];
      }
      

      И выполните загрузку:

      Downloader *downloader = [[Downloader alloc] initWithDelegate:self];
      [downloader downloadAsynchronously:url filename:filename];
      NSLog(@"%s done", __FUNCTION__);
      
    • Шаблон селектора: шаблон, который вы видите в некоторых объектах Cocoa (например, NSTimer, UIPanGestureRecognizer) - это понятие передачи селектора в качестве параметра. Например, мы могли бы определить наш метод загрузки следующим образом:

      - (NSURLSessionTask *)downloadAsynchronously:(NSURL *)url filename:(NSString *)filename target:(id)target selector:(SEL)selector {
          id __weak weakTarget = target; // so that the dispatch_async won't retain the selector
      
          NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
              dispatch_async(dispatch_get_main_queue(), ^{
      #pragma clang diagnostic push
      #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                  [weakTarget performSelector:selector
                                   withObject:data
                                   withObject:filename];
      #pragma clang diagnostic pop
              });
          }];
          [task resume];
      
          return task;
      }
      

      Затем вы вызываете это следующим образом:

      [self downloadAsynchronously:url
                          filename:filename
                            target:self
                          selector:@selector(didFinishedDownload:filename:)];
      

      Но вы также должны определить этот отдельный метод, который будет вызываться при загрузке:

      - (void)didFinishedDownload:(NSData *)data filename:(NSString *)filename {
          NSLog(@"Downloaded %d bytes", [data length]);
          [data writeToFile:filename atomically:YES];
      }
      

      Лично я считаю, что этот шаблон слишком хрупкий и зависит от координации интерфейсов без какой-либо помощи от компилятора. Но я включаю его для некоторой исторической ссылки, учитывая, что этот шаблон используется довольно немного в Cocoa старых классах.

    • Notifications: Другой механизм предоставления результатов некоторого асинхронного метода - отправить локальное уведомление. Это обычно наиболее полезно, когда либо (а) потенциальный получатель результатов сетевого запроса неизвестен в момент начала запроса; или (b) может быть несколько классов, которые хотят получать информацию об этом событии. Таким образом, сетевой запрос может публиковать уведомление о конкретном имени, когда оно было сделано, и любой объект, который заинтересован в информировании об этом событии, может добавить себя в качестве наблюдателя для этого локального уведомления через NSNotificationCenter.

      Это не "обратный вызов" как таковой, но представляет собой другой шаблон для объекта, который должен быть проинформирован о завершении некоторой асинхронной задачи.

Вот несколько примеров шаблонов обратного вызова. Очевидно, что приведенный пример был произвольным и тривиальным, но, надеюсь, он должен дать вам представление о ваших альтернативах. В настоящее время два наиболее распространенных метода - это блоки и шаблоны делегатов. Блоки становятся все более предпочтительными, когда требуется простой и элегантный интерфейс. Но для богатых и сложных интерфейсов делегаты очень распространены.