delegate function vs callback function [duplicate]

2019-01-21 12:04发布

问题:

This question already has an answer here:

  • Problem understanding Delegating pattern & callback functions in Objective C 3 answers

I work on iOS platform , I want to know what is a delegate function and what is a callback function ? what is the difference between the two types of function or they are the same ??

example of delegate function is numberOfRowsInSection in UITableViewDelegate protocol and example of callback function is didReceiveLocalNotification in appDelegate.m

Can we create our own callback function in Objective-C , if YES ,give an example ...

Thank you..

回答1:

A couple of thoughts:

  1. You suggest that didReceiveLocationNotification was a "callback function", but it's actually just a delegate method of the UIApplicationDelegate protocol. So, both numberOfRowsInSection and didReceiveLocalNotification are simply delegate methods.

    Something more akin to a generic callback function would be the selector when scheduling a NSTimer or defining the handler for a UIGestureRecognizer, where the choice of method name is not predetermined.

    Or callbacks are used extensively in CFArray.

  2. But, the root of your question is less on the terminology, but rather a question of how to define an interface where the caller can specify a method that some other object will invoke (asynchronously) at some future date. There are a couple of common patterns:

    • Block parameter to method: It is increasingly common to define methods that take a block as a parameter. For example, you can have a method that is defined as follows:

      - (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;
      }
      

      That third parameter, completion, is a block of code that will be called with the download is done. Thus, you can invoke that method as follows:

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

      You'll see that "done" message appear immediately, and that completion block will be called when the download is done. It admittedly takes a while to get used to the ungainly mess of punctuation that constitutes a block variable/parameter definition, but once you're familiar with the block syntax, you'll really appreciate this pattern. It eliminates the disconnect between the invoking of some method and the defining of some separate callback function.

      If you want to simplify the syntax of dealing with blocks as parameters, you can actually define a typedef for your completion block:

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

      And then the method declaration, itself, is simplified:

      - (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: The other common technique for communicating between objects is the delegate-protocol pattern. First, you define the protocol (the nature of the "callback" interface):

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

      Then, you define your class that will be invoking this DownloadDelegate method:

      @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
      

      And finally, the original view controller which uses this new Downloader class must conform to the DownloadDelegate protocol:

      @interface ViewController () <DownloadDelegate>
      
      @end
      

      And define the protocol method:

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

      And perform the download:

      Downloader *downloader = [[Downloader alloc] initWithDelegate:self];
      [downloader downloadAsynchronously:url filename:filename];
      NSLog(@"%s done", __FUNCTION__);
      
    • Selector pattern: A pattern that you see in some Cocoa objects (e.g. NSTimer, UIPanGestureRecognizer) is the notion of passing a selector as a parameter. For example, we could have defined our downloader method as follows:

      - (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;
      }
      

      You'd then invoke that as follows:

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

      But you also have to define that separate method that will be called when the download is done:

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

      Personally, I find this pattern to be far too fragile and dependent upon coordinating interfaces without any assistance from the compiler. But I include it for a bit of historical reference given that this pattern is used quite a bit in Cocoa's older classes.

    • Notifications: The other mechanism to provide the results of some asynchronous method is to send a local notification. This is generally most useful when either (a) the potential recipient of the results of the network request is unknown at the time the request was initiated; or (b) there may be multiple classes that want to be informed of this event. So the network request can post a notification of a particular name when it's done, and any object that is interested in being informed of this event can add themselves as an observer for that local notification through the NSNotificationCenter.

      This is not a "callback" per se, but does represent another pattern for an object to be informed of the completion of some asynchronous task.

Those are a few examples of "callback" patterns. Clearly, the example provided was arbitrary and trivial, but hopefully it should give you an idea of your alternatives. The two most common techniques, nowadays, are blocks and delegate patterns. Blocks are increasingly being preferred when a simple and elegant interface is needed. But for rich and complicated interfaces, delegates are very common.