简体   繁体   English

带块参数的NSInvocation

[英]NSInvocation with block arguments

I'm trying to pass block arguments to a NSInvocation , but the app crashes. 我正在尝试将块参数传递给NSInvocation ,但应用程序崩溃了。 The invocation makes a network request and calls the success or failure blocks. 调用发出网络请求并调用成功或失败块。 I think the problem is that blocks are dealloced before the network request finishes. 我认为问题是在网络请求完成之前会释放块。 I managed to get it to work with some Block_copy hackery and it doesn't report any leaks using Instruments. 我设法让它与一些Block_copy hackery一起工作,并且它不使用Instruments报告任何泄漏。

Questions: - Is it possible that the leak is there even though the Static Analyzer or Instruments is not reporting it? 问题: - 即使静态分析仪或仪器没有报告,泄漏是否可能存在? - Is there a better way to "retain" the block? - 有没有更好的方法来“保留”这个区块?

// Create the NSInvocation
NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector];
NSInvocation* invoc = [NSInvocation invocationWithMethodSignature:methodSignature];
[invoc setTarget:target];
[invoc setSelector:selector];

// Create success and error blocks.
void (^successBlock)(id successResponse) = ^(id successResponse) {
    // Some success code here ...
};

void (^errorBlock)(NSError *error) = ^(NSError *error) {
    // Some failure code here ...
};

/*
Without the two Block_copy lines, the block gets dealloced too soon
and the app crashes with EXC_BAD_ACCESS
I tried [successBlock copy] and [failureBlock copy] instead,
but the app still crashes.
It seems like Block_copy is the only way to move the block to the heap in this case.
*/
Block_copy((__bridge void *)successBlock);
Block_copy((__bridge void *)errorBlock);
// Set the success and failure blocks.
[invoc setArgument:&successBlock atIndex:2];
[invoc setArgument:&errorBlock atIndex:3];

[invoc retainArguments]; // does not retain blocks

// Invoke the method.
[invoc invoke];

Update: I updated the code to below. 更新:我将代码更新到下面。 The blocks are NSMallocBlocks , but the app still crashes. 块是NSMallocBlocks ,但应用程序仍然崩溃。

// Create success and error blocks.
int i = 0;
void (^successBlock)(id successResponse) = ^(id successResponse) {
    NSLog(@"i = %i", i);
    // Some success code here ...
};

void (^errorBlock)(NSError *error) = ^(NSError *error) {
    NSLog(@"i = %i", i);
    // Some failure code here ...
};

/*** Both blocks are NSMallocBlocks here ***/
// Set the success and failure blocks.
void (^successBlockCopy)(id successResponse) = [successBlock copy];
void (^errorBlockCopy)(NSError *error) = [errorBlock copy];

/*** Both blocks are still NSMallocBlocks here - I think copy is a NoOp ***/

// Set the success and failure blocks.
[invoc setArgument:&successBlockCopy atIndex:2];
[invoc setArgument:&errorBlockCopy atIndex:3];

[invoc retainArguments]; // does not retain blocks

// Invoke the method.
[invoc invoke];

The blocks are passed down in the chain as follows: 这些块在链中传递如下:

NSInvocationNSProxy ( NSInvocation using forwardInvocation: ) → method1methodN NSInvocationNSProxy (使用forwardInvocation: NSInvocation forwardInvocation: )→ method1methodN

methodN eventually calls the success or failure block depending on the HTTP response. methodN最终根据HTTP响应调用成功或失败块。

Do I need to copy the block at every stage? 我是否需要在每个阶段复制块? The example above was talking about the first NSInvocation . 上面的例子讨论了第一个NSInvocation Do I also need [invocation retainArguments]; 我还需要[invocation retainArguments]; at every appropriate step? 在每个适当的步骤? I'm using ARC. 我正在使用ARC。

Block_copy , and indeed [block copy] return copies. Block_copy ,实际上[block copy] 返回副本。 They don't magically switch the original with a copy at the same location. 他们不会在同一位置用副本神奇地切换原件。 So at the very least I think you want: 所以至少我认为你想要:

successBlock = Block_copy((__bridge void *)successBlock);
errorBlock = Block_copy((__bridge void *)errorBlock); 

(or, equivalently, successBlock = [successBlock copy]; ... ) (或者,相当于, successBlock = [successBlock copy]; ...

Otherwise you're creating copies, doing nothing with them and still passing the originals off to the invocation. 否则,您正在创建副本,不对它们执行任何操作,仍然将原始文件传递给调用。

EDIT: so, I put the following code into a project: 编辑:所以,我把以下代码放入一个项目:

@interface DummyClass: NSObject
@end

typedef void (^ successBlock)(id successResponse);
typedef void (^ failureBlock)(NSError *error);

@implementation DummyClass

- (id)init
{
    self = [super init];

    if(self)
    {
        SEL selector = @selector(someMethodWithSuccess:failure:);
        id target = self;

        // Create the NSInvocation
        NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector];
        NSInvocation* invoc = [NSInvocation invocationWithMethodSignature:methodSignature];
        [invoc setTarget:target];
        [invoc setSelector:selector];

        // Create success and error blocks.
        void (^successBlock)(id successResponse) = ^(id successResponse) {
            // Some success code here ...
            NSLog(@"Off, off, off with %@", successResponse);
        };

        void (^errorBlock)(NSError *error) = ^(NSError *error) {
            // Some failure code here ...
            NSLog(@"Dance, dance, dance till %@", error);
        };

        successBlock = [successBlock copy];
        errorBlock = [errorBlock copy];

        // Set the success and failure blocks.
        [invoc setArgument:&successBlock atIndex:2];
        [invoc setArgument:&errorBlock atIndex:3];

        [invoc retainArguments]; // does not retain blocks

        // 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(),
        ^{
            [invoc invoke];

        });
    }

    return self;
}

- (void)someMethodWithSuccess:(successBlock)successBlock failure:(failureBlock)failureBlock
{
    NSLog(@"Words:");
    successBlock(@[@"your", @"head"]);
    failureBlock([NSError errorWithDomain:@"you're dead" code:0 userInfo:nil]);
}

@end

And added the following to the end of application:didFinishLaunchingWithOptions: : 并在application:didFinishLaunchingWithOptions:结束时添加以下内容application:didFinishLaunchingWithOptions: ::

DummyClass *unusedInstance = [[DummyClass alloc] init];

The result is that two seconds after launching my program the following appears on the console: 结果是在启动程序两秒后,控制台上出现以下内容:

2013-06-02 20:11:56.057 TestProject[3330:c07] Words:
2013-06-02 20:11:56.059 TestProject[3330:c07] Off, off, off with (
    your,
    head
)
2013-06-02 20:11:56.060 TestProject[3330:c07] Dance, dance, dance till Error Domain=you're dead Code=0 "The operation couldn’t be completed. (you're dead error 0.)"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM