NSInvocation带有块参数

7
我试图将block参数传递给NSInvocation,但是应用程序崩溃了。该调用发出网络请求并调用成功或失败的块。我认为问题在于块在网络请求完成之前被释放了。我设法通过一些Block_copy技巧使其正常工作,并且使用Instruments没有报告任何泄漏。

问题: - 即使Static Analyzer或Instruments没有报告,是否可能存在泄漏? - 有更好的“保留”块的方法吗?

// 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];

更新:我将代码更新如下。这些块是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];

区块会按照以下方式在链中传递:
NSInvocation → NSProxy(使用forwardInvocation的NSInvocation)→ method1 → methodN
methodN最终根据HTTP响应调用成功或失败的block。
我需要在每个阶段都复制该块吗? 上面的示例是关于第一个NSInvocation的。 我还需要在每个适当的步骤上使用[invocation retainArguments]; 吗? 我正在使用ARC。
1个回答

8

Block_copy[block copy]确实会返回拷贝。它们不会在原位置神奇地切换原始块和副本。所以,至少我认为你想要:

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

(或者等价地,successBlock = [successBlock copy]; ...)

否则你会创建副本,对它们什么也不做,仍然将原始值传递给调用。

编辑:所以,我将以下代码放入项目中:

@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

application:didFinishLaunchingWithOptions:的末尾添加以下内容:

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

两秒钟后启动我的程序,控制台上会出现以下内容:
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.)"

我曾经认为调用Block_copy会强制将块保存在堆栈上而不是堆上。但我仍然无法理解为什么将 [successBlock copy] 传递给调用而不是 successBlock 不能正常工作。 - pshah
2
如果您在创建后立即调用“invocation”,则无需复制块。如果被调用的方法异步执行,则由其负责复制它们。如果您不是异步调用invocation实例或将其保存以备将来使用,则无需复制块。 - Sulthan
1
实际上,我的代码块可能是不好的示例,因为它们实际上没有捕获任何状态,所以在释放后使用它们是安全的并不奇怪——代码块中的代码在编译时由编译器编译,只有被捕获的状态属于该代码块。 - Tommy
1
那么,无论你看到的崩溃是什么,肯定与你的块复制或不复制无关。Malloc块已经在堆上了,保留它们就足够了。 - Tommy
1
一个问题是 successBlock = [successBlock copy]; errorBlock = [errorBlock copy]; 会导致内存泄漏... 我需要在任何地方释放它们吗? - Mihir Mehta
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接