ios 委托函数 vs 回调函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15597601/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
delegate function vs callback function
提问by Subbu
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 ??
我在 iOS 平台上工作,我想知道什么是委托函数,什么是回调函数?这两种类型的功能有什么区别或者它们是一样的??
example of delegate function is numberOfRowsInSection
in UITableViewDelegate
protocol and example of callback function is didReceiveLocalNotification
in appDelegate.m
委托函数的例子numberOfRowsInSection
在UITableViewDelegate
协议中,回调函数的例子didReceiveLocalNotification
在appDelegate.m
Can we create our own callback function in Objective-C , if YES ,give an example ...
我们可以在 Objective-C 中创建我们自己的回调函数吗,如果是,举个例子......
Thank you..
谢谢..
回答by Rob
A couple of thoughts:
一些想法:
You suggest that
didReceiveLocationNotification
was a "callback function", but it's actually just a delegate method of theUIApplicationDelegate
protocol. So, bothnumberOfRowsInSection
anddidReceiveLocalNotification
are simply delegate methods.Something more akin to a generic callback function would be the
selector
when scheduling aNSTimer
or defining the handler for aUIGestureRecognizer
, where the choice of method name is not predetermined.Or callbacks are used extensively in
CFArray
.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 theDownloadDelegate
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.
你认为这
didReceiveLocationNotification
是一个“回调函数”,但它实际上只是UIApplicationDelegate
协议的一个委托方法。因此,numberOfRowsInSection
和didReceiveLocalNotification
都只是委托方法。更类似于通用回调函数的东西是
selector
在调度 aNSTimer
或定义 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; }
第三个参数,
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
类的原始视图控制器必须符合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
.这本身不是“回调” ,但确实代表了另一种模式,让对象被告知某个异步任务的完成。
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.
这些是“回调”模式的几个例子。显然,提供的示例是随意且琐碎的,但希望它能让您了解您的替代方案。现在,两种最常见的技术是块和委托模式。当需要一个简单而优雅的界面时,块越来越受到青睐。但是对于丰富复杂的接口,委托是很常见的。