33 votes

Comment simplifier la logique de rappel avec un bloc ?

Disons que j'ai besoin de communiquer avec une classe qui fournit un protocole et appelle des méthodes déléguées lorsqu'une opération est terminée, comme suit :

@protocol SomeObjectDelegate

@required
- (void)stuffDone:(id)anObject;
- (void)stuffFailed;

@end

@interface SomeObject : NSObject
{
}
@end

Maintenant, j'ai décidé que pendant que je pourrait faire en sorte qu'une autre classe implémente le stuffDone: j'ai décidé que je préférais encapsuler le processus dans un bloc qui est écrit quelque part à proximité de l'endroit où se trouve la méthode de délégation. SomeObject est instancié, appelé, etc. Comment pourrais-je faire cela ? Ou en d'autres termes, si vous regardez este célèbre article sur les blocs (dans la section Replace Callbacks) ; comment pourrais-je écrire une méthode dans SomeObject qui accepte un completionHandler: en quelque sorte ?

42voto

CRD Points 21578

Il semble que vous souhaitiez communiquer avec une classe existante qui est conçue pour prendre un objet délégué. Il existe un certain nombre d'approches, notamment :

  1. en utilisant une catégorie pour ajouter des variantes basées sur les blocs des méthodes appropriées ;
  2. utiliser une classe dérivée pour ajouter les variantes basées sur les blocs ; et
  3. écrire une classe qui implémente le protocole et appelle vos blocs.

Voici une façon de procéder (3). Tout d'abord, supposons que votre SomeObject est :

@protocol SomeObjectDelegate
@required
- (void)stuffDone:(id)anObject;
- (void)stuffFailed;

@end

@interface SomeObject : NSObject
{
}

+ (void) testCallback:(id<SomeObjectDelegate>)delegate;

@end

@implementation SomeObject

+ (void) testCallback:(id<SomeObjectDelegate>)delegate
{
    [delegate stuffDone:[NSNumber numberWithInt:42]];
    [delegate stuffFailed];
}

@end

ainsi nous avons un moyen de tester - vous aurez un vrai SomeObject.

Définissez maintenant une classe qui implémente le protocole et appelle vos blocs fournis :

#import "SomeObject.h"

typedef void (^StuffDoneBlock)(id anObject);
typedef void (^StuffFailedBlock)();

@interface SomeObjectBlockDelegate : NSObject<SomeObjectDelegate>
{
    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

Cette classe enregistre les blocs que vous lui passez et les appelle en réponse aux rappels du protocole. L'implémentation est simple :

@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

La seule chose dont vous devez vous souvenir est de Block_copy() les blocs lors de l'initialisation et de Block_release() plus tard - c'est parce que les blocs sont alloués par la pile et que votre objet peut dépasser sa trame de pile de création ; Block_copy() crée une copie dans le tas.

Maintenant vous pouvez toutes les méthodes basées sur les délégués en leur passant des blocs :

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

Vous pouvez utiliser cette technique pour envelopper des blocs pour n'importe quel protocole.

Addendum à l'ARC

En réponse au commentaire : pour rendre ce système compatible avec l'ARC, il suffit de supprimer les appels à Block_copy() quitter les affectations directes :

stuffDoneCallback = done;
stuffFailedCallback = fail;

et retirer le dealloc méthode. Vous pouvez également modifier Blockcopy a copy c'est-à-dire stuffDoneCallback = [done copy]; et c'est ce que vous pouvez supposer être nécessaire en lisant la documentation de l'ARC. Cependant, ce n'est pas le cas, car l'affectation est faite à une variable forte, ce qui amène l'ARC à conserver la valeur affectée - et la conservation d'un bloc de pile la copie dans le tas. Par conséquent, le code ARC généré produit les mêmes résultats avec ou sans l'attribut copy .

7voto

Dave DeLong Points 156978

Vous pourriez faire quelque chose comme ça :

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;

Puis à l'intérieur SomeObject que vous pourriez faire :

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

1voto

HDA Points 2590

Le lien ci-dessous explique comment les rappels utilisant des délégués pourraient être facilement remplacés par des blocs.

Les exemples comprennent UITableview, UIAlertview et ModalViewController.

cliquez sur moi

J'espère que cela vous aidera.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X