1. 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:
- (void)downloadAsynchronously:(NSURL *)url
filename:(NSString *)filename
completion:(void (^)(NSData *results, NSString *filename))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
completion(data, filename);
});
});
}
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:
- (void)downloadAsynchronously:(NSURL *)url
filename:(NSString *)filename
completion:(DownloadCompletionBlock)completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
completion(data, filename);
});
});
}
- (void)downloadAsynchronously:(NSURL *)url
filename:(NSString *)filename
completion:(void (^)(NSData *results, NSString *filename))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
completion(data, filename);
});
});
}
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:
- (void)downloadAsynchronously:(NSURL *)url
filename:(NSString *)filename
completion:(DownloadCompletionBlock)completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
completion(data, filename);
});
});
}
2Delegate-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>
- (void)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;
- (id)initWithDelegate:(id<DownloadDelegate>)delegate;
- (void)downloadAsynchronously:(NSURL *)url
filename:(NSString *)filename;
@end
@implementation Downloader
- (id)initWithDelegate:(id<DownloadDelegate>)delegate
{
self = [super init];
if (self) {
_delegate = delegate;
}
return self;
}
- (void)downloadAsynchronously:(NSURL *)url
filename:(NSString *)filename
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate didFinishedDownload:data filename:filename];
});
});
}
@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__);
@protocol DownloadDelegate <NSObject>
- (void)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;
- (id)initWithDelegate:(id<DownloadDelegate>)delegate;
- (void)downloadAsynchronously:(NSURL *)url
filename:(NSString *)filename;
@end
@implementation Downloader
- (id)initWithDelegate:(id<DownloadDelegate>)delegate
{
self = [super init];
if (self) {
_delegate = delegate;
}
return self;
}
- (void)downloadAsynchronously:(NSURL *)url
filename:(NSString *)filename
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate didFinishedDownload:data filename:filename];
});
});
}
@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__);
3Selector 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:
- (void)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
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
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
});
});
}
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.
- (void)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
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
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
});
});
}
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.
参考:http://stackoverflow.com/questions/15597601/delegate-function-vs-callback-function