Tuttavia, la radice della domanda è meno sulla terminologia, ma piuttosto una questione su come definire un'interfaccia in cui il chiamante può specificare un metodo che alcuni altri invocheranno (in modo asincrono) in una data futura. Ci sono un paio di modelli comuni:
Block parameter to method: È sempre più comune per definire metodi che accettano un blocco come parametro. Ad esempio, è possibile avere un metodo che viene definito come segue:
- (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;
}
Questo terzo parametro, completion
, è un blocco di codice che verrà chiamato con il download è fatto. Così, si può richiamare il metodo come segue:
[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__);
Vedrai che il messaggio "fatto" appare immediatamente, e che completion
blocco verrà chiamato quando il download è fatto. Ci vuole un po 'per abituarsi al disordinato pasticcio della punteggiatura che costituisce una variabile di blocco/definizione di parametro, ma una volta che hai familiarità con la sintassi del blocco, apprezzerai davvero questo schema. Elimina la disconnessione tra il richiamo di alcuni metodi e la definizione di alcune funzioni di callback separate.
Se si vuole semplificare la sintassi di trattare con blocchi come parametri, si può effettivamente definire un typedef
per il blocco completo:
typedef void (^DownloadCompletionBlock)(NSData *results, NSString *filename);
E poi la dichiarazione di metodo, in sé, è semplificata:
- (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: L'altra tecnica comune per la comunicazione tra gli oggetti è il modello del protocollo delegato.In primo luogo, si definisce il protocollo (la natura dell'interfaccia "callback"):
@protocol DownloadDelegate <NSObject>
- (NSURLSessionTask *)didFinishedDownload:(NSData *)data filename:(NSString *)filename;
@end
Poi, si definisce la classe che sarà invoca questo metodo 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
E, infine, l'originale View Controller che utilizza questo nuovo Downloader
classe deve essere conforme al protocollo DownloadDelegate
:
@interface ViewController() <DownloadDelegate>
@end
e definire il protocollo di me ThOD:
- (void)didFinishedDownload:(NSData *)data filename:(NSString *)filename {
NSLog(@"Downloaded %d bytes", [data length]);
[data writeToFile:filename atomically:YES];
}
ed effettuare il download:
Downloader *downloader = [[Downloader alloc] initWithDelegate:self];
[downloader downloadAsynchronously:url filename:filename];
NSLog(@"%s done", __FUNCTION__);
Selector pattern: Un modello che si vede in alcuni oggetti di cacao (per esempio NSTimer
, UIPanGestureRecognizer
) è la nozione di passare un selettore come parametro. Per esempio, si può definire il nostro metodo downloader come segue:
- (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;
}
Farebbe quindi richiamare che nel modo seguente:
[self downloadAsynchronously:url
filename:filename
target:self
selector:@selector(didFinishedDownload:filename:)];
Ma bisogna anche definire che metodo separato che verrà chiamato quando il download è fatto:
- (void)didFinishedDownload:(NSData *)data filename:(NSString *)filename {
NSLog(@"Downloaded %d bytes", [data length]);
[data writeToFile:filename atomically:YES];
}
Personalmente, trovo questo schema di essere troppo fragile e dipendente da coordinare le interfacce senza alcuna assistenza da parte del compilatore. Ma lo includo per un po 'di riferimenti storici dato che questo pattern è usato un po' nelle classi più datate di Cocoa.
Notifications: l'altro meccanismo per fornire i risultati di un metodo asincrono consiste nell'inviare una notifica locale. Ciò è generalmente più utile quando (a) il potenziale destinatario dei risultati della richiesta di rete è sconosciuto al momento in cui la richiesta è stata avviata; oppure (b) potrebbero esserci più classi che vogliono essere informate di questo evento. Pertanto, la richiesta di rete può inviare una notifica di un nome particolare quando viene eseguita e qualsiasi oggetto interessato a essere informato di questo evento può aggiungersi come osservatore per quella notifica locale tramite lo NSNotificationCenter
.
Questo non è un "callback" di per sé, ma rappresenta un altro modello per un oggetto da informare del completamento di alcune attività asincrone.
Guarda le risposte fornite su http://stackoverflow.com/questions/12050981/when-we-use-delegate-and-call-back-in-ios –