如何用Block简化回调逻辑?

Ala*_*ino 33 cocoa objective-c objective-c-blocks

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

@protocol SomeObjectDelegate

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

@end

@interface SomeObject : NSObject
{
}
@end
Run Code Online (Sandbox Code Playgroud)

现在,我已经决定,虽然我可以创建另一个类实现stuffDone:委托方法,但我决定将该进程封装到一个块中,该块写在靠近SomeObject实例化,调用的地方等等.我怎么可能做这个?或者换句话说,如果你看看对块著名的文章(在替换回调段); 我如何在SomeObject中编写一个接受各种类型的方法completionHandler:

CRD*_*CRD 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
Run Code Online (Sandbox Code Playgroud)

所以我们有一些方法可以测试 - 你将有一个真正的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
Run Code Online (Sandbox Code Playgroud)

此类保存您传入的块并调用它们以响应协议回调.实施很简单:

@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
Run Code Online (Sandbox Code Playgroud)

您唯一需要记住的是Block_copy()初始化时的块和稍后的Block_release() - 这是因为块是堆栈分配的,你的对象可能比它的创建堆栈帧寿命更长; Block_copy()在堆中创建一个副本.

现在你可以通过它传递所有基于委托的方法块:

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

您可以使用此技术来包装任何协议的块.

ARC附录

回应评论:要使此ARC兼容,只需删除Block_copy()离开直接分配的呼叫:

stuffDoneCallback = done;
stuffFailedCallback = fail;
Run Code Online (Sandbox Code Playgroud)

并删除该dealloc方法.您也可以更改Blockcopycopy,即stuffDoneCallback = [done copy];,这是您在阅读ARC文档时可能需要的内容.然而,并不是因为赋值是一个强大的变量,导致ARC保留指定的值 - 并保留堆栈块将其复制到堆中.因此,无论是否生成,生成的ARC代码都会产生相同的结果copy.


Dav*_*ong 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;
Run Code Online (Sandbox Code Playgroud)

在里面SomeObject,你可以这样做:

if ([self hadError]) {
  callback([self error]);
} else {
  callback(nil);
}
Run Code Online (Sandbox Code Playgroud)