如何使用Block简化回调逻辑?

34

假设我需要与一个提供协议并在操作完成时调用委托方法的类进行通信,如下所示:

@protocol SomeObjectDelegate

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

@end

@interface SomeObject : NSObject
{
}
@end

现在,我已经决定虽然我可以让另一个类实现stuffDone:代理方法,但我更想将该过程封装到一个块中,在某个靠近SomeObject实例化、调用等的位置编写该块。我该怎么做?换句话说,如果你看一下这篇文章关于块的(在替换回调部分); 我该如何编写一个在SomeObject中接受一种completionHandler:的方法?

3个回答

42

看起来你想要与一个旨在接受代理对象的现有类通信。有许多方法,包括:

  1. 使用分类添加基于块的适当方法变体;
  2. 使用派生类添加基于块的变体;和
  3. 编写实现协议并调用您的块的类。

这里是一种实现(3)的方法。首先,假设你的SomeObject是:

@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

我们有一些测试方法 - 你将拥有一个真实的SomeObject。

现在定义一个实现协议并调用你提供的代码块的类:

#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

这个类保存你传入的块,并响应协议回调时调用它们。实现很简单:

@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

你需要记住的唯一一件事是在初始化时使用Block_copy()对块进行复制,并稍后使用Block_release()释放它们 - 这是因为块是堆栈分配的,你的对象可能会比创建堆栈帧更长寿;Block_copy()将在堆中创建一个副本。

现在,你可以调用委托方法并传递块:

[SomeObject testCallback:[SomeObjectBlockDelegate
                                  someObjectBlockDelegateWithOnDone:^(id anObject) { NSLog(@"Done: %@", anObject); }
                                  andOnFail:^{ NSLog(@"Failed"); }
                                  ]
]; 
你可以使用这种技术来为任何协议包装块。 ARC 补充说明 针对评论所述:要使其与 ARC 兼容,只需删除对 Block_copy() 的调用,直接进行赋值即可。
stuffDoneCallback = done;
stuffFailedCallback = fail;

同时移除dealloc方法。你也可以将Blockcopy改为copy,即stuffDoneCallback = [done copy];,并且这可能是从阅读ARC文档中所需的。然而,由于赋值给了一个强引用变量,导致ARC保留了赋值的值,并且保留堆栈块会将其复制到堆上。因此,生成的ARC代码无论是否使用copy都会产生相同的结果。


你能解释一下这部分使用ARC的代码吗?stuffDoneCallback = Block_copy(done); stuffFailedCallback = Block_copy(fail); 它告诉我需要进行桥接转换。 - zeiteisen
@zeiteisen - 很好的问题。你现在可能已经知道答案了,抱歉我没有看到评论。但是为了未来的访问者,我已经添加了一个附录。 - CRD

7
你可以这样做:

你可以像这样做:

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;

然后在SomeObject内部,您可以执行以下操作:

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

请问您能否解释一下为什么我们需要复制这个代码块? - Rashmi Ranjan mallick

1
下面的链接解释了如何使用块替换使用委托的回调函数。
示例包括UITableview、UIAlertview和ModalViewController。 点击这里 希望这可以帮助你。

2
请查看http://stackoverflow.com/help/how-to-answer。具体而言,遵循以下准则可以改进此答案:“始终引用重要链接的最相关部分,以防目标站点无法访问或永久离线。” - Ben Collins

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接