Passing blocks to asynchronous methods

自古美人都是妖i 提交于 2019-12-23 20:07:26

问题


I'm passing a block to an asynchronous method which executes this block later. My app crashes if I don't copy the block before passing it to someMethod:success:failure:

Is there a way to copy the blocks in forwardInvocation: instead of copying it before passing it to someMethod:success:failure: ?

The flow is someMethod:success:failure: -> forwardInvocation: -> httpGet:success:failure

httpGet:success:failure: executes the success or the failure block depending on the HTTP status code.

// AppDelegate.h

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) id response;
@property (strong, nonatomic) NSError *error;

@end

// AppDelegate.m

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // The app crashes if the blocks are not copied here!
    [[MyController new] someMethod:[^(NSString *response) {
        self.response = response;
        NSLog(@"response = %@", response);
    } copy] failure:[^(NSError *error) {
        self.error = error;
    } copy]];

    return YES;
}

@end

// MyController.h

@protocol MyControllerProtocol <NSObject>

@optional


- (void)someMethod:(void (^)(NSString *response))success
           failure:(void (^)(NSError *error))failure;

@end

@interface MyController : NSObject <MyControllerProtocol>

@end

// MyController.m

#import "MyController.h"
#import "HTTPClient.h"

@implementation MyController

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation retainArguments];

    NSUInteger numberOfArguments = [[invocation methodSignature] numberOfArguments];

    typedef void(^SuccessBlock)(id object);
    typedef void(^FailureBlock)(NSError *error);

    __unsafe_unretained SuccessBlock successBlock1;
    __unsafe_unretained SuccessBlock failureBlock1;
    [invocation getArgument:&successBlock1 atIndex:(numberOfArguments - 2)]; // success block is always the second to last argument (penultimate)
    SuccessBlock successBlock = [successBlock1 copy];
    [invocation getArgument:&failureBlock1 atIndex:(numberOfArguments - 1)]; // failure block is always the last argument
    FailureBlock failureBlock = [failureBlock1 copy];

    NSLog(@"successBlock copy = %@", successBlock);
    NSLog(@"failureBlock copy = %@", failureBlock);

    // Simulates a HTTP request and calls the success block later!
    [HTTPClient httpGet:@"somerequest" success:successBlock failure:failureBlock];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:sel];
    return methodSignature;
}

@end


// HTTPClient.h

@interface HTTPClient : NSObject

+ (void)httpGet:(NSString *)path
        success:(void (^)(id object))success
        failure:(void (^)(NSError *error))failure;

@end

// HTTPClient.m

#import "HTTPClient.h"

@implementation HTTPClient

+ (void)httpGet:(NSString *)path
        success:(void (^)(id object))success
        failure:(void (^)(NSError *error))failure
{
    // Invoke the method.
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(),
               ^{
                   success(@"foo");
               });
}

@end

The complete source code can be found here: https://github.com/priteshshah1983/BlocksWithNSInvocation

Can you please help?


回答1:


The culprit is the line [invocation retainArguments]. If you comment out that line, it works fine. (That line was never necessary anyway because the invocation is never stored or used asynchronously.)

Explanation:

Think about what -retainArguments does. It calls retain on all the arguments of object pointer type. And then when the invocation is deallocated, it calls release on them.

But the arguments are stack (non-copied) blocks. retain and release have no effect on it (since it's not a heap object). So when it is retained, nothing happens, and then (from the crash) it appears that the invocation was autoreleased at some point (a perfectly normal thing to happen), so the final release and deallocation of the invocation happens asynchronously. When the invocation is deallocated, it tries to release its retained arguments, but by then it's trying to message a no-longer valid stack block, causing a crash.

P.S. the copying of the blocks in forwardInvocation: was also unnecessary



来源:https://stackoverflow.com/questions/17606802/passing-blocks-to-asynchronous-methods

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!