委托函数与回调函数

时间:2013-03-24 10:57:52

标签: ios objective-c delegates callback

我在iOS平台上工作,我想知道什么是委托函数,什么是回调函数?这两种函数之间的区别是什么?它们是相同的?

代理功能的示例是numberOfRowsInSection协议中的UITableViewDelegate,回调函数的示例是didReceiveLocalNotification中的appDelegate.m

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

谢谢..

1 个答案:

答案 0 :(得分:69)

有几点想法:

  1. 您建议didReceiveLocationNotification是“回调函数”,但它实际上只是UIApplicationDelegate协议的委托方法。因此,numberOfRowsInSectiondidReceiveLocalNotification都只是委托方法。

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

    或回调广泛用于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将自己添加为该本地通知的观察者。

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

  3. 这些是“回调”模式的几个例子。显然,提供的示例是任意而微不足道的,但希望它能让您了解您的替代方案。如今,两种最常见的技术是块和委托模式。当需要简单而优雅的界面时,块越来越受欢迎。但对于丰富而复杂的界面,代表很常见。