34

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

@protocol SomeObjectDelegate

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

@end

@interface SomeObject : NSObject
{
}
@end

现在,我决定虽然我可以让另一个类实现stuffDone:委托方法,但我决定我宁愿将进程封装到一个块中,该块写在靠近SomeObject实例化、调用等位置的地方。我怎么能做这个?或者换句话说,如果你看这篇关于块的著名文章(在替换回调部分);我如何在 SomeObject 中编写一个接受某种completionHandler:类型的方法?

4

3 回答 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方法。您也可以更改Blockcopycopy, ie stuffDoneCallback = [done copy];,这可能是您在阅读 ARC 文档时所需要的。然而,这并不是因为分配给一个强变量会导致 ARC 保留分配的值 - 并且保留堆栈块会将其复制到堆中。因此,生成的 ARC 代码无论是否使用copy.

于 2011-01-30T03:15:45.547 回答
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);
}
于 2011-01-28T08:22:00.287 回答
1

下面的链接解释了使用委托的回调如何可以很容易地替换为块。

示例包括 UITableview、UIAlertview 和 ModalViewController。

点我

希望这可以帮助。

于 2013-08-19T19:33:07.260 回答