【问题标题】:NSInvocation with block arguments带有块参数的 NSInvocation
【发布时间】:2013-06-03 02:28:06
【问题描述】:

我正在尝试将块参数传递给 NSInvocation,但应用程序崩溃了。该调用发出网络请求并调用成功或失败块。我认为问题是在网络请求完成之前块被释放。我设法让它与一些 Block_copyhackery 一起工作,并且它没有使用 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];

区块在链中的传递如下:

NSInvocationNSProxy (NSInvocation 使用forwardInvocation:) → method1methodN

methodN 最终会根据 HTTP 响应调用成功或失败块。

我需要在每个阶段复制块吗?上面的例子是在谈论第一个NSInvocation。在每个适当的步骤中我是否还需要[invocation retainArguments];?我正在使用 ARC。

【问题讨论】:

    标签: ios objective-c-blocks dealloc nsinvocation


    【解决方案1】:

    Block_copy,实际上是[block copy] return 副本。他们不会神奇地将原件与同一位置的副本交换。所以至少我认为你想要:

    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.)"
    

    【讨论】:

    • 我试过了 successBlock = [successBlock copy];和 errorBlock = [errorBlock 副本];但我遇到了同样的崩溃,这个错误:地址不包含指向目标文件中的一个部分的部分。正如我所提到的,添加提到的 Block_copy 行可以防止崩溃,但我不确定它们是否会泄漏内存。
    • 如果您在创建后立即调用invocation,则无需复制块。如果异步执行某些操作,则被调用方法负责复制它们。如果您不是异步调用调用实例或保存它以供将来使用,则无需复制块。
    • 实际上,我的块可能是不好的例子,因为它们实际上并没有捕获任何状态,所以它们在释放后可以安全使用也就不足为奇了——块中的代码是由编译器在编译时编译的时间,只有捕获的状态归区块所有。
    • 那么你看到的任何崩溃肯定与你的块复制或不复制无关。 Malloc 块已经在堆上,保留它们就足够了。
    • 一个问题是successBlock = [successBlock copy]; errorBlock = [errorBlock 副本];会导致内存泄漏...我需要在任何地方释放它们吗??
    猜你喜欢
    • 2015-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-12
    • 1970-01-01
    相关资源
    最近更新 更多