2012-04-03 3 views
4

NSInvocations에 대해 배울 때 메모리 관리에 대한 이해가 부족한 것처럼 보입니다. 여기 NSInvocation 및 NSError - __autoreleasing 및 메모리 크래셔

은 샘플 프로젝트입니다 : 물론

@interface DoNothing : NSObject 
@property (nonatomic, strong) NSInvocation *invocation; 
@end 

@implementation DoNothing 
@synthesize invocation = _invocation; 

NSString *path = @"/Volumes/Macintosh HD/Users/developer/Desktop/string.txt"; 

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

     SEL selector = @selector(stringWithContentsOfFile:encoding:error:); 
     NSInvocation *i = [NSInvocation invocationWithMethodSignature:[NSString methodSignatureForSelector:selector]]; 

     Class target = [NSString class]; 
     [i setTarget:target]; 
     [i setSelector:@selector(stringWithContentsOfFile:encoding:error:)]; 

     [i setArgument:&path atIndex:2]; 

     NSStringEncoding enc = NSASCIIStringEncoding; 
     [i setArgument:&enc atIndex:3]; 

     __autoreleasing NSError *error; 
     __autoreleasing NSError **errorPointer = &error; 
     [i setArgument:&errorPointer atIndex:4]; 

     // I understand that I need to declare an *error in order to make sure 
     // that **errorPointer points to valid memory. But, I am fuzzy on the 
     // __autoreleasing aspect. Using __strong doesn't prevent a crasher. 

     [self setInvocation:i]; 
    } 

    return self; 
} 

@end 

, 내가 여기서하고있어 모든 그것은 이해하게는 NSString 클래스 메서드

+[NSString stringWithContentsOfFile:(NSString \*)path encoding:(NSStringEncoding)enc error:(NSError \**)error] 

에 대한 속성으로 호출 객체를 구축 특히, this blog post을 읽은 후, ** errorPointer에 주소를 선언하고 할당하여 NSError 객체를 처리해야하는 이유에 대해 설명합니다. __autoreleasing 및 메모리 관리는 여기서 일어나는 일을 파악하기가 조금 어렵습니다.

** errorPointer 변수는 개체가 아니므로 보유 횟수가 없습니다. 단순히 NSError 객체를 가리키는 메모리 주소를 저장하는 메모리입니다. 나는 stringWith ... 메소드가 NSError 객체를 alloc, init, autorelease하고 * errorPointer = 할당 된 메모리를 설정할 것이라는 것을 이해한다. 나중에 보 겠지만 NSError 객체는 액세스 할 수 없게됩니다. 이것은 ...

  • ... 자동 릴리스 풀이 비워 졌기 때문에 ...?
  • ... ARC가 stringWith ...의 alloc + init에 대한 "release"호출을 채웠기 때문에? 그래서

의이 호출이 "작동"하는 방법을 살펴 보자

int main(int argc, const char * argv[]) 
{ 
    @autoreleasepool { 

     NSError *regularError = nil; 
     NSString *aReturn = [NSString stringWithContentsOfFile:path 
                 encoding:NSASCIIStringEncoding 
                 error:&regularError]; 

     NSLog(@"%@", aReturn); 

     DoNothing *thing = [[DoNothing alloc] init]; 
     NSInvocation *invocation = [thing invocation]; 

     [invocation invoke]; 

     __strong NSError **getErrorPointer; 
     [invocation getArgument:&getErrorPointer atIndex:4]; 
     __strong NSError *getError = *getErrorPointer; // CRASH! EXC_BAD_ACCESS 

     // It doesn't really matter what kind of attribute I set on the NSError 
     // variables; it crashes. This leads me to believe that the NSError 
     // object that is pointed to is being deallocated (and inspecting with 
     // NSZombies on, confirms this). 

     NSString *bReturn; 
     [invocation getReturnValue:&bReturn]; 
    } 
    return 0; 
} 

이있다 나는 내가 할 때 무엇을하고 있었는지 도대체 알고 있다고 생각대로 된 아이 오프닝 나를 위해 (당황 약간) 그것은 메모리 관리에왔다!

내 crashher를 해결하기 위해 할 수있는 최선의 방법은 NSError * 오류 변수를 init 메서드에서 꺼내 전역 변수로 만드는 것입니다. 이 때문에 ** errorPointer에서 __autoreleasing에서 __strong으로 속성을 변경해야했습니다. 그러나 특히 작업 대기열에서 NSInvocations를 여러 번 재사용 할 가능성이 높다는 점을 고려하면 수정 사항이 이상적이지 않음을 분명히 알 수 있습니다. 그것도 단지 종류는 오류가 dealloc'd되고 내 의심을 확인합니다.

최종 WTF로 __ 브리지 캐스트를 사용하여 약간 시도해 보았습니다.하지만 1. 내가 여기 필요한지 확실하지 않습니다. 2. 순열 후에 효과가있는 것을 찾을 수 없었습니다.

나는이 모든 것이 클릭되지 않는 이유를 더 잘 이해할 수 있도록 도움이되는 몇 가지 통찰력을 알고 싶습니다.

답변

6

이것은 실제로 Automatic Reference Counting과는 아무런 관련이없는 매우 간단한 오류입니다. -[DoNothing init]에서

, 당신은 스택 변수에 대한 포인터로 호출의 오류 매개 변수를 초기화하고 있습니다 :

__autoreleasing NSError *error; 
__autoreleasing NSError **errorPointer = &error; 
[i setArgument:&errorPointer atIndex:4]; 

그리고 main, 당신은 같은 포인터 것을 잡아하고 그것을 역 참조 :

__strong NSError **getErrorPointer; 
[invocation getArgument:&getErrorPointer atIndex:4]; 
__strong NSError *getError = *getErrorPointer; 

물론이 시점에서 -[DoNothing init]에있는 모든 로컬 변수는 더 이상 존재하지 않으므로 여기에서 읽으려고하면 충돌이 발생합니다.

+0

Gotcha! * error' 그래서 정말 내가'정적 NSError를 __strong로 NSError * 오류를 선언해야 아니, – edelaney05

+0

아 (? 그것은 힙 변수 만들기도 합리적인 말을한다는 것입니다? 나는 ... 스택/힙 구현 세부라고 생각) 그것을 고정시키지 마십시오. 'NSInvocation'을 클래스의 공용 부분으로 삼지 마십시오. 버그는 유용하지 않게 된 후에 액세스 할 수 있기 때문에 발생합니다. * 반드시 *해야하는 경우 오류 변수를 클래스의 속성으로 만듭니다. (덧붙여 말하자면, 스택 대 힙 (heap)은 구현 세부 사항이 아니며 기본 객체 수명 개념입니다. –

+0

오류 인수는 호출이 시작된 후에 만 ​​유용합니다. 타이머 또는 작업 큐에서 호출을 수행하므로 오류 변수가 범위를 벗어날 가능성이 거의 있습니다. 그러나, 그것이 왜 충돌하고 있는지에 관한 일반적인 요지는 이제 명백하게 결정되었습니다 - 감사합니다! 이 모든 객체가'NSInvocation' 래퍼 인 경우 – edelaney05