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:
?
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:
- użycie kategorii do dodania wariantów blokowych odpowiednich metod;
- Użyj klasy pochodnej, aby dodać warianty oparte na blokach; oraz
- 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.
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);
}
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.
Mam nadzieję, że to pomoże.
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