【问题标题】:delegate function vs callback function [duplicate]委托函数与回调函数[重复]
【发布时间】:2013-03-13 22:02:53
【问题描述】:

我在iOS平台工作,我想知道什么是委托函数,什么是回调函数?这两种功能有什么区别还是一样??

委托函数的例子是UITableViewDelegate协议中的numberOfRowsInSection,回调函数的例子是appDelegate.m中的didReceiveLocalNotification

我们可以在Objective-C中创建自己的回调函数吗,如果可以,举个例子...

谢谢你..

【问题讨论】:

标签: ios objective-c delegates callback


【解决方案1】:

一些想法:

  1. 您认为didReceiveLocationNotification 是一个“回调函数”,但它实际上只是UIApplicationDelegate 协议的一个委托方法。所以,numberOfRowsInSectiondidReceiveLocalNotification 都是简单的委托方法。

    在调度NSTimer 或为UIGestureRecognizer 定义处理程序时,selector 更类似于通用回调函数,其中方法名称的选择不是预先确定的。

    或者回调在CFArray中被广泛使用。

  2. 但是,您的问题的根源不是术语,而是如何定义接口的问题,调用者可以在其中指定某个其他对象将在将来某个日期(异步)调用的方法。有几种常见的模式:

    • Block parameter to method:定义将块作为参数的方法越来越普遍。例如,您可以有一个定义如下的方法:

      - (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__);
      

      您会立即看到“完成”消息,下载完成后将调用 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;
      } 
      
    • Delegate-protocol pattern:对象之间通信的另一种常用技术是委托协议模式。首先,您定义协议(“回调”接口的性质):

      @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__);
      
    • Selector pattern:在一些 Cocoa 对象中看到的模式(例如NSTimerUIPanGestureRecognizer)是将选择器作为参数传递的概念。例如,我们可以如下定义下载器方法:

      - (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:提供一些异步方法结果的另一种机制是发送本地通知。这通常在以下情况下最有用: (a) 网络请求结果的潜在接收者在发起请求时未知;或 (b) 可能有多个班级希望获悉此事件。因此,网络请求可以在完成后发布特定名称的通知,任何有兴趣了解此事件的对象都可以通过NSNotificationCenter 将自己添加为该本地通知的观察者。

      这不是“回调”本身,,但确实代表了另一种模式,用于通知对象完成某些异步任务。

这些是“回调”模式的一些示例。显然,提供的示例是任意且琐碎的,但希望它能让您了解您的替代方案。如今,两种最常见的技术是块模式和委托模式。当需要简单而优雅的界面时,块越来越受到青睐。但是对于丰富复杂的接口,委托就很常见了。

【讨论】:

  • @rob 非常有帮助的帖子谢谢 :)
  • @Rob 如何选择使用委托模式或块?我不知道我们是否可以谈论“最佳”实践,但您更喜欢- (void)didFinishedDownload:(NSData *)data filename:(NSString *)filename- (void)downloadAsynchronously:(NSURL *)url filename:(NSString *)filename completion:(void (^)(NSData *results, NSString *filename))completion 之间的哪种方式?
  • 就个人而言,我更喜欢简单情况下的块(例如,只有一个,甚至可能是两个回调)。但是如果开始变得比这更复杂,那么我开始倾向于委托模式。在您的示例中,我可能倾向于块实现。
  • 我不太确定如何通过嵌套 disptach_async 来获取 dataWithContentsOfURL 的响应?
  • @powerfade917 - 这些模式中的每一个都说明了在稍后的某个时间点将异步网络请求的结果发回的不同方式。请注意,我们不会像异步调用/通知其他对象异步任务完成那样“返回”数据。 (顺便说一句,我已经删除了 dataWithContentsOfURL,因为这是发起网络请求的可怕模式,在上面的示例中我已经用 NSURLSession 替换了它。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-20
  • 2021-05-31
  • 2020-04-29
  • 2012-09-30
相关资源
最近更新 更多