NSInvocations について学んでいると、メモリ管理についての理解にギャップがあるように思えます。
サンプル プロジェクトは次のとおりです。
@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]
特にこのブログ投稿を読んだ後では、**errorPointer にアドレスを宣言して割り当てることによって NSError オブジェクトを処理する必要がある理由が理解できます。把握するのが少し難しいのは、ここで起こっている __autoreleases とメモリ管理です。
**errorPointer 変数はオブジェクトではないため、保持カウントはありません。NSError オブジェクトを指すメモリ アドレスを格納するのは、単にメモリです。stringWith... メソッドが NSError オブジェクトを割り当て、初期化し、自動解放し、*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:®ularError];
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;
}
メモリ管理に関しては、自分が何をしているのか知っていると思っていたので、これは私にとって目を見張るものでした(少し戸惑いました)。
クラッシャーを解決するために私ができる最善の方法は、init メソッドから NSError *error 変数を取り出してグローバルにすることです。これにより、**errorPointer の属性を __autoreleasing から __strong に変更する必要がありました。しかし、操作キューで NSInvocations を何度も再利用する可能性が高いことを考えると、この修正は理想的ではないことは明らかです。また、*エラーが割り当て解除されているという私の疑いを確認するだけです。
最後の WTF として、__bridge キャストを少しいじってみましたが、1. それがここで必要なものかどうかわかりません。2. 並べ替えた後、機能するものが見つかりませんでした。
これがうまくいかない理由をよりよく理解するのに役立つかもしれない洞察が欲しい.