ただし、質問の根本は用語ではなく、呼び出し元が他のオブジェクトが将来(非同期に)呼び出すメソッドを指定できるインターフェイスを定義する方法の問題です。一般的なパターンがいくつかあります。
メソッドへのブロックパラメーター:ブロックをパラメーターとして受け取るメソッドを定義することがますます一般的になっています。たとえば、次のように定義されたメソッドを持つことができます。
- (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オブジェクト(たとえば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の古いクラスでかなり使用されていることを考えると、少し歴史的な参照のためにそれを含めます。
通知:非同期メソッドの結果を提供する他のメカニズムは、ローカル通知を送信することです。これは一般に、次のいずれかの場合に最も役立ちます。(a)要求が開始された時点で、ネットワーク要求の結果の潜在的な受信者が不明である。または(b)このイベントの通知を希望するクラスが複数存在する場合があります。したがって、ネットワークリクエストは、特定の名前の通知が完了すると投稿できます。このイベントの通知に関心のあるオブジェクトは、を介してそのローカル通知のオブザーバーとして自分自身を追加できNSNotificationCenter
ます。
これはそれ自体が「コールバック」ではありませんが、非同期タスクの完了を通知されるオブジェクトの別のパターンを表しています。