How to simplify callback logic with a Block?

前端 未结 3 1435
一生所求
一生所求 2020-11-29 17:30

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 SomeObjectD         


        
相关标签:
3条回答
  • 2020-11-29 18:10

    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.

    0 讨论(0)
  • 2020-11-29 18:21

    The below link explains how the call backs using delegates could be easily replaced with blocks.

    The examples includes UITableview,UIAlertview and ModalViewController.

    click me

    Hope this helps.

    0 讨论(0)
  • 2020-11-29 18:23

    You could do something like this:

    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;
    

    Then inside SomeObject, you could do:

    if ([self hadError]) {
      callback([self error]);
    } else {
      callback(nil);
    }
    
    0 讨论(0)
提交回复
热议问题