14

iOSプラットフォームで作業していますが、デリゲート関数とは何か、コールバック関数とは何ですか?2種類の機能の違いは何ですか?それとも同じですか?

デリゲート関数の例はnumberOfRowsInSectionプロトコルUITableViewDelegateにあり、コールバック関数の例はにありdidReceiveLocalNotificationますappDelegate.m

Objective-Cで独自のコールバック関数を作成できますか?YESの場合、例を挙げてください...

ありがとうございました..

4

1 に答える 1

70

いくつかの考え:

  1. これは「コールバック関数」であるとおっしゃっていdidReceiveLocationNotificationますが、実際にはUIApplicationDelegateプロトコルのデリゲートメソッドにすぎません。したがって、とは両方ともnumberOfRowsInSection単にdidReceiveLocalNotificationデリゲートメソッドです。

    一般的なコールバック関数に似たものは、メソッド名の選択が事前に決定されていない場合に、selectorをスケジュールするとき、NSTimerまたはのハンドラーを定義するときです。UIGestureRecognizer

    または、コールバックはで広く使用されていCFArrayます。

  2. ただし、質問の根本は用語ではなく、呼び出し元が他のオブジェクトが将来(非同期に)呼び出すメソッドを指定できるインターフェイスを定義する方法の問題です。一般的なパターンがいくつかあります。

    • メソッドへのブロックパラメーター:ブロックをパラメーターとして受け取るメソッドを定義することがますます一般的になっています。たとえば、次のように定義されたメソッドを持つことができます。

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

      その3番目のパラメーター、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;
      } 
      
    • デリゲートプロトコルパターン:オブジェクト間で通信するための他の一般的な手法は、デリゲートプロトコルパターンです。まず、プロトコル(「コールバック」インターフェースの性質)を定義します。

      @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クラスを使用する元のView Controllerは、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オブジェクト(たとえば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の古いクラスでかなり使用されていることを考えると、少し歴史的な参照のためにそれを含めます。

    • 通知:非同期メソッドの結果を提供する他のメカニズムは、ローカル通知を送信することです。これは一般に、次のいずれかの場合に最も役立ちます。(a)要求が開始された時点で、ネットワーク要求の結果の潜在的な受信者が不明である。または(b)このイベントの通知を希望するクラスが複数存在する場合があります。したがって、ネットワークリクエストは、特定の名前の通知が完了すると投稿できます。このイベントの通知に関心のあるオブジェクトは、を介してそのローカル通知のオブザーバーとして自分自身を追加できNSNotificationCenterます。

      これはそれ自体が「コールバック」ではありませんが、非同期タスクの完了を通知されるオブジェクトの別のパターンを表しています。

これらは「コールバック」パターンのいくつかの例です。明らかに、提供された例は恣意的で些細なものでしたが、うまくいけば、それはあなたにあなたの代替案のアイデアを与えるはずです。現在、最も一般的な2つの手法は、ブロックとデリゲートパターンです。シンプルでエレガントなインターフェースが必要な場合、ブロックがますます好まれています。しかし、リッチで複雑なインターフェースの場合、デリゲートは非常に一般的です。

于 2013-03-24T16:17:59.843 に答える