Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
942 views
in Technique[技术] by (71.8m points)

objective c - How to simplify callback logic with a Block?

Let's say I need to communicate with a class that provides a protocol and calls delegate methods when an operation is complete, as so:

@protocol SomeObjectDelegate

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

@end

@interface SomeObject : NSObject
{
}
@end

Now, I've decided that while I could make another class implement the stuffDone: delegate method, I've decided that I'd rather encapsulate the process into a block which is written somewhere close to where SomeObject is instantiated, called, etc. How might I do this? Or in other words, if you look at this famous article on blocks (in the Replace Callbacks section); how might I write a method in SomeObject that accepts a completionHandler: of sorts?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It sounds like you wish to communicate with an existing class which is designed to take a delegate object. There are a number of approaches, including:

  1. using a category to add block-based variants of the appropriate methods;
  2. use a derived class to add the block-based variants; and
  3. write a class which implements the protocol and calls your blocks.

Here is one way to do (3). First let's assume your SomeObject is:

@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

so we have some way to test - you will have a real SomeObject.

Now define a class which implements the protocol and calls your supplied blocks:

#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

This class saves the blocks you pass in and calls them in response to the protocol callbacks. The implementation is straightforward:

@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

The only thing you need to remember is to Block_copy() the blocks when initializing and to Block_release() them later - this is because blocks are stack allocated and your object may outlive its creating stack frame; Block_copy() creates a copy in the heap.

Now you can all a delegate-based method passing it blocks:

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

You can use this technique to wrap blocks for any protocol.

ARC Addendum

In response to the comment: to make this ARC compatible just remove the calls to Block_copy() leaving direct assignments:

stuffDoneCallback = done;
stuffFailedCallback = fail;

and remove the dealloc method. You can also change Blockcopy to copy, i.e. stuffDoneCallback = [done copy];, and this is what you might assume is needed from reading the ARC documentation. However it is not as the assignment is to a strong variable which causes ARC to retain the assigned value - and retaining a stack block copies it to the heap. Therefore the ARC code generated produces the same results with or without the copy.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...