Come semplificare la logica di callback con un blocco?

Diciamo che ho bisogno di comunicare con una class che fornisce un protocollo e chiama i metodi delegati quando un’operazione è completa, in questo modo:

@protocol SomeObjectDelegate @required - (void)stuffDone:(id)anObject; - (void)stuffFailed; @end @interface SomeObject : NSObject { } @end 

Ora, ho deciso che mentre potevo fare un’altra class implementare il metodo stuffDone: delegate, ho deciso che preferivo incapsulare il processo in un blocco che è scritto da qualche parte vicino a dove SomeObject è istanziato, chiamato, ecc. Come potrei farlo? O in altre parole, se si guarda questo famoso articolo sui blocchi (nella sezione Sostituisci callback); come posso scrivere un metodo in SomeObject che accetta un completionHandler: di sorta?

Sembra che tu voglia comunicare con una class esistente progettata per prendere un object delegato. Esistono numerosi approcci, tra cui:

  1. utilizzando una categoria per aggiungere varianti basate su blocchi dei metodi appropriati;
  2. utilizzare una class derivata per aggiungere le varianti basate su blocchi; e
  3. scrivi una class che implementa il protocollo e chiama i tuoi blocchi.

Ecco un modo per fare (3). Per prima cosa supponiamo che il tuo SomeObject sia:

 @protocol SomeObjectDelegate @required - (void)stuffDone:(id)anObject; - (void)stuffFailed; @end @interface SomeObject : NSObject { } + (void) testCallback:(id)delegate; @end @implementation SomeObject + (void) testCallback:(id)delegate { [delegate stuffDone:[NSNumber numberWithInt:42]]; [delegate stuffFailed]; } @end 

quindi abbiamo un modo per testare – avrete un object SomeObject reale.

Ora definisci una class che implementa il protocollo e chiama i blocchi forniti:

 #import "SomeObject.h" typedef void (^StuffDoneBlock)(id anObject); typedef void (^StuffFailedBlock)(); @interface SomeObjectBlockDelegate : NSObject { StuffDoneBlock stuffDoneCallback; StuffFailedBlock stuffFailedCallback; } - (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail; - (void)dealloc; + (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail; // protocol - (void)stuffDone:(id)anObject; - (void)stuffFailed; @end 

Questa class salva i blocchi che si passano e li chiama in risposta al callback del protocollo. L’implementazione è semplice:

 @implementation SomeObjectBlockDelegate - (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail { if (self = [super init]) { // copy blocks onto heap stuffDoneCallback = Block_copy(done); stuffFailedCallback = Block_copy(fail); } return self; } - (void)dealloc { Block_release(stuffDoneCallback); Block_release(stuffFailedCallback); [super dealloc]; } + (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail { return (SomeObjectBlockDelegate *)[[[SomeObjectBlockDelegate alloc] initWithOnDone:done andOnFail:fail] autorelease]; } // protocol - (void)stuffDone:(id)anObject { stuffDoneCallback(anObject); } - (void)stuffFailed { stuffFailedCallback(); } @end 

L’unica cosa che devi ricordare è Block_copy () i blocchi durante l’inizializzazione e Block_release () in seguito – questo perché i blocchi sono allocati allo stack e il tuo object potrebbe sopravvivere alla creazione dello stack frame; Block_copy () crea una copia nell’heap.

Ora è ansible bloccare tutti i metodi basati su un delegato:

 [SomeObject testCallback:[SomeObjectBlockDelegate someObjectBlockDelegateWithOnDone:^(id anObject) { NSLog(@"Done: %@", anObject); } andOnFail:^{ NSLog(@"Failed"); } ] ]; 

È ansible utilizzare questa tecnica per avvolgere blocchi per qualsiasi protocollo.

Addendum ARC

In risposta al commento: per rendere questo ARC compatibile basta rimuovere le chiamate a Block_copy() lasciando assegnazioni dirette:

 stuffDoneCallback = done; stuffFailedCallback = fail; 

e rimuovere il metodo dealloc . Puoi anche cambiare Blockcopy da copy , ovvero stuffDoneCallback = [done copy]; e questo è ciò che si potrebbe supporre è necessario dalla lettura della documentazione ARC. Tuttavia non è come l’assegnazione è a una variabile forte che fa sì che ARC mantenga il valore assegnato – e il mantenimento di un blocco di stack lo copia nell’heap. Pertanto il codice ARC generato produce gli stessi risultati con o senza la copy .

Potresti fare qualcosa del genere:

 typedef void (^AZCallback)(NSError *); AZCallback callback = ^(NSError *error) { if (error == nil) { NSLog(@"succeeded!"); } else { NSLog(@"failed: %@", error); } }; SomeObject *o = [[SomeObject alloc] init]; [o setCallback:callback]; // you *MUST* -copy the block [o doStuff]; ...etc; 

Quindi all’interno di SomeObject , puoi fare:

 if ([self hadError]) { callback([self error]); } else { callback(nil); } 

Il seguente link spiega come i richiami usando i delegati potrebbero essere facilmente sostituiti con i blocchi.

Gli esempi includono UITableview, UIAlertview e ModalViewController.

cliccami

Spero che questo ti aiuti.