2013-06-03 3 views
7

블록 인수를 NSInvocation에 전달하려고 시도했지만 앱이 충돌합니다. 호출은 네트워크 요청을 작성하고 성공 또는 실패 블록을 호출합니다. 문제는 네트워크 요청이 완료되기 전에 블록 할당이 해제된다는 것입니다. 나는 Block_copy hackery로 작동하도록 만들었지 만, Instruments를 사용하여 누수를보고하지 않습니다.블록 인수가있는 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]; 

블록은 체인에서 아래로 전달됩니다 결국 HTTP에 따라 성공 또는 실패 블록을 호출

methodNmethodNmethod1

NSInvocationNSProxy (forwardInvocation:NSInvocation 사용) 응답.

모든 단계에서 블록을 복사해야합니까? 위의 예는 첫 번째 NSInvocation에 대한 이야기입니다. 적절한 단계마다 [invocation retainArguments];이 필요합니까? ARC를 사용하고 있습니다.

답변

8

Block_copy 실제로는 [block copy] 사본입니다. 그들은 마술 같은 위치에서 사본으로 원본을 전환하지 않습니다. 내가 생각하는 아주 최소한 그래서 당신이 원하는 :

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

(또는, 동등하게, successBlock = [successBlock copy]; ...를) 그렇지 않으면, 사본을 만들어 그들과 함께 아무것도하지 않고 여전히 호출에 떨어져 원본을 전달하고 있습니다.

편집은 다음 application:didFinishLaunchingWithOptions:의 끝에 다음

@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 

그리고 추가 : 그래서, 나는 프로젝트에 다음 코드를 넣어

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

시도해 보았습니다. successBlock = [successBlock cop 와이]; 및 errorBlock = [오류 블록 복사]; 하지만이 오류와 동일한 충돌이 발생합니다. 주소에 개체 파일의 섹션을 가리키는 섹션이 포함되어 있지 않습니다. . 앞에서 언급 한 것처럼 Block_copy 라인을 추가하면 충돌을 방지 할 수 있지만 메모리가 누설되는지는 확실하지 않습니다. – pshah

+0

현재 사용중인'Block_copy'에는 문서화 된 효과가 없습니다. 보시다시피, 문제가되는'invoke '에 의해 정의되지 않은 결과는 다른 정의되지 않은 효과를 갖습니다. 진정한 해결책은 아닙니다. 그리고 좀비조차도 스택 객체를 인위적으로 유지할 수 없기 때문에 여기에서 디버그하는 것을 도와주지 않습니다. 스택이 다시 커지면 스택 객체를 덮어 씁니다. – Tommy

+0

Block_copy를 호출하면 블록이 스택 대신 힙에 저장됩니다. 그리고, 나는 여전히 successBlock 대신에 [successBlock copy]를 호출에 전달하는 것이 왜 효과가 있는지 파악할 수 없다. – pshah