Jak uprościć logikę wywołania zwrotnego za pomocą bloku?

Załóżmy, że muszę komunikować się z klasą, która dostarcza protokół i wywołuje metody delegata, gdy operacja jest zakończona, tak:

@protocol SomeObjectDelegate

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

@end

@interface SomeObject : NSObject
{
}
@end

Teraz zdecydowałem, że podczas gdy mógłbym sprawić, by inna klasa zaimplementowała metodę delegowania stuffDone:, zdecydowałem, że wolałbym raczej zamknąć proces w bloku, który jest napisany gdzieś blisko miejsca, w którym SomeObject jest tworzona instancja, wywoływana itp. Jak mam to zrobić? Lub innymi słowy, jeśli spojrzysz na to słynny artykuł o blokach (w sekcji Replace Callbacks); Jak mogę napisać metodę w jakimś obiekcie, która akceptuje completionHandler:?

Author: Alan Zeino, 2011-01-28

3 answers

Wygląda na to, że chcesz się komunikować z istniejącą klasą, która jest zaprojektowana tak, aby przyjmować obiekt delegata. Istnieje wiele podejść, w tym:

  1. użycie kategorii do dodania wariantów blokowych odpowiednich metod;
  2. Użyj klasy pochodnej, aby dodać warianty oparte na blokach; oraz
  3. napisz klasę, która implementuje protokół i wywołuje twoje bloki.

Oto jeden ze sposobów (3). Najpierw Załóżmy, że Twój SomeObject jest:

@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

Więc mamy jakiś sposób, aby przetestować - będziesz miał prawdziwy SomeObject.

Zdefiniuj teraz klasę, która implementuje protokół i wywołuje dostarczone przez Ciebie bloki:

#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

Ta klasa zapisuje bloki, które przekazujesz i wywołuje je w odpowiedzi na wywołania protokołu. Implementacja jest prosta:

@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

Jedyne, co musisz pamiętać, to Block_copy () bloki podczas inicjalizacji i block_release () je później - dzieje się tak dlatego, że bloki blok_copy() tworzy kopię na stosie, a obiekt może przeżyć swoją ramkę stosu; Block_copy () tworzy kopię na stosie.

Teraz możesz wszystkie metody oparte na delegatach, przekazując je blokom:

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

Możesz użyć tej techniki do zawijania bloków dla dowolnego protokołu.

ARC Addendum

W odpowiedzi na komentarz: aby ten ARC był zgodny wystarczy usunąć wywołania do Block_copy() pozostawiając bezpośrednie przypisania:

stuffDoneCallback = done;
stuffFailedCallback = fail;

I usunąć metodę dealloc. Można również Zmień Blockcopy na copy, tzn. stuffDoneCallback = [done copy];, i możesz założyć, że jest to potrzebne z czytania dokumentacji ARC. Jednak nie jest tak, jak przypisanie do silnej zmiennej, która powoduje, że ARC zachowuje przypisaną wartość - a zachowanie bloku stosu kopiuje go do sterty. W związku z tym wygenerowany kod ARC daje takie same wyniki z copy lub bez niego.

 42
Author: CRD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-13 02:56:30

Możesz zrobić coś takiego:

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;

Wtedy w środku SomeObject możesz zrobić:

if ([self hadError]) {
  callback([self error]);
} else {
  callback(nil);
}
 7
Author: Dave DeLong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-28 08:22:00

Poniższy link wyjaśnia, w jaki sposób wywołania zwrotne przy użyciu delegatów można łatwo zastąpić blokami.

Przykłady obejmują UITableview, UIAlertview i ModalViewController.

Kliknij mnie

Mam nadzieję, że to pomoże.

 1
Author: Durai Amuthan.H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-19 19:33:07