如何用Block简化回调逻辑?

时间:2011-01-28 01:49:22

标签: objective-c cocoa objective-c-blocks

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

@protocol SomeObjectDelegate

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

@end

@interface SomeObject : NSObject
{
}
@end

现在,我已经决定,虽然我可以使另一个类实现stuffDone:委托方法,但我决定将该进程封装到一个块中,写在靠近SomeObject实例化,调用等的地方。我该怎么做?或者换句话说,如果你看一下关于块的this着名文章(在Replace Callbacks部分中);我如何在SomeObject中编写一个接受completionHandler:种类的方法?

3 个答案:

答案 0 :(得分:42)

听起来您希望与现有的类进行通信,该类旨在获取委托对象。有很多方法,包括:

  1. 使用类别添加适当方法的基于块的变体;
  2. 使用派生类添加基于块的变体;和
  3. 编写一个实现协议的类并调用您的块。
  4. 这是一种方法(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保留指定的值 - 并保留一个堆栈块将其复制到堆中。因此,无论是否有copy,生成的ARC代码都会产生相同的结果。

答案 1 :(得分: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);
}

答案 2 :(得分:1)

以下链接说明了如何使用代理轻松替换使用代理的回调。

示例包括UITableview,UIAlertview和ModalViewController。

click me

希望这有帮助。