Rob*_*Rob 69
几点想法:
你建议这didReceiveLocationNotification是一个"回调函数",但它实际上只是UIApplicationDelegate协议的委托方法.所以,无论是numberOfRowsInSection和didReceiveLocalNotification是简单的委托方法.
类似于通用回调函数的东西将是selector在调度a NSTimer或定义a的处理程序时UIGestureRecognizer,其中方法名称的选择未预先确定.
或者回调被广泛使用CFArray.
但是,问题的根源不在于术语,而在于如何定义一个接口的问题,其中调用者可以指定某个其他对象将在某个未来日期调用(异步)的方法.有几种常见的模式:
方法的块参数:定义将块作为参数的方法越来越常见.例如,您可以使用如下定义的方法:
- (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;
}
Run Code Online (Sandbox Code Playgroud)
第三个参数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__);
Run Code Online (Sandbox Code Playgroud)
您将看到"完成"消息立即出现,并且completion在下载完成后将调用该块.无可否认地需要一段时间才能习惯构成块变量/参数定义的标点符号,但是一旦熟悉了块语法,你就会非常欣赏这种模式.它消除了调用某些方法与定义某些单独的回调函数之间的脱节.
如果要简化将块作为参数处理的语法,可以实际typedef为完成块定义一个:
typedef void (^DownloadCompletionBlock)(NSData *results, NSString *filename);
Run Code Online (Sandbox Code Playgroud)
然后简化方法声明本身:
- (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;
}
Run Code Online (Sandbox Code Playgroud)委托协议模式:对象之间通信的另一种常用技术是委托协议模式.首先,定义协议("回调"接口的性质):
@protocol DownloadDelegate <NSObject>
- (NSURLSessionTask *)didFinishedDownload:(NSData *)data filename:(NSString *)filename;
@end
Run Code Online (Sandbox Code Playgroud)
然后,您定义将调用此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
Run Code Online (Sandbox Code Playgroud)
最后,使用这个新Downloader类的原始视图控制器必须符合DownloadDelegate协议:
@interface ViewController () <DownloadDelegate>
@end
Run Code Online (Sandbox Code Playgroud)
并定义协议方法:
- (void)didFinishedDownload:(NSData *)data filename:(NSString *)filename {
NSLog(@"Downloaded %d bytes", [data length]);
[data writeToFile:filename atomically:YES];
}
Run Code Online (Sandbox Code Playgroud)
并执行下载:
Downloader *downloader = [[Downloader alloc] initWithDelegate:self];
[downloader downloadAsynchronously:url filename:filename];
NSLog(@"%s done", __FUNCTION__);
Run Code Online (Sandbox Code Playgroud)选择模式:您在一些Cocoa对象见A模式(例如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;
}
Run Code Online (Sandbox Code Playgroud)
然后,您将调用如下:
[self downloadAsynchronously:url
filename:filename
target:self
selector:@selector(didFinishedDownload:filename:)];
Run Code Online (Sandbox Code Playgroud)
但是您还必须定义在下载完成时将调用的单独方法:
- (void)didFinishedDownload:(NSData *)data filename:(NSString *)filename {
NSLog(@"Downloaded %d bytes", [data length]);
[data writeToFile:filename atomically:YES];
}
Run Code Online (Sandbox Code Playgroud)
就个人而言,我发现这种模式过于脆弱,并且在没有编译器帮助的情况下依赖于协调接口.但是我将它包含在一些历史参考中,因为这个模式在Cocoa的旧类中使用了很多.
通知:提供某种异步方法结果的另一种机制是发送本地通知.当(a)在发起请求时未知网络请求结果的潜在接收者时,这通常是最有用的; 或(b)可能有多个班级想要被告知此事件.因此,网络请求可以在完成后发布特定名称的通知,并且任何有兴趣被通知此事件的对象都可以通过自身添加自己作为该本地通知的观察者NSNotificationCenter.
这本身不是"回调" ,但确实代表了一个对象被告知某个异步任务完成的另一种模式.
这些是"回调"模式的几个例子.显然,提供的示例是任意而微不足道的,但希望它能让您了解您的替代方案.如今,两种最常见的技术是块和委托模式.当需要简单而优雅的界面时,块越来越受欢迎.但对于丰富而复杂的界面,代表很常见.