2013-06-03 41 views
7

我試圖將塊參數傳遞給NSInvocation,但該應用程序崩潰。該調用發出網絡請求並調用成功或失敗塊。我認爲問題是在網絡請求完成之前塊已經交易。我設法讓它與一些Block_copy hackery一起工作,並且它不使用工具報告任何泄漏。帶塊參數的NSInvocation

問題: - 即使靜態分析儀或儀器未報告泄漏,是否有泄漏存在? - 有沒有更好的方法來「保留」該塊?

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

的塊在鏈中向下傳遞如下:(使用NSInvocationforwardInvocation:

NSInvocationNSProxymethod1methodN

methodN取決於HTTP最終調用的成功或失敗塊響應。

我需要在每個階段複製塊嗎?上面的例子是關於第一個NSInvocation。在每個適當的步驟中,我是否也需要[invocation retainArguments];?我正在使用ARC。

回答

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

結果是2秒下水後我控制檯上出現如下程序:

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

我試了successBlock = [successBlock cop Y];和errorBlock = [errorBlock copy];但是我得到了與此錯誤相同的崩潰:地址不包含指向對象文件 中某個部分的部分。正如我所提到的那樣,添加Block_copy行可以防止崩潰,但我不確定它們是否會泄漏內存。 – pshah

+0

您目前使用的'Block_copy'沒有記錄的效果。你所看到的是,由有問題的'invoke'引起的未定義結果具有不同的未定義效果。這不是一個真正的解決方案。甚至殭屍也不會幫助你在這裏進行調試,因爲它們不能保持堆棧對象的人爲活動 - 一旦堆棧增長回來,它將覆蓋它們。 – Tommy

+0

我的印象是,調用Block_copy強制將塊保存在堆而不是堆棧上。而且,我仍然無法弄清楚爲什麼傳遞[successBlock copy]而不是successBlock來調用不起作用。 – pshah