在学习NSInvocations时,似乎我在理解内存管理方面存在差距。NSInvocation&NSError - __autoreleasing&memory crasher
下面是一个简单的项目:
@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 ...方法会分配init,并自动释放一个NSError对象,并设置* errorPointer =分配的内存。正如你稍后会看到的,NSError对象变得不可访问。这是...
- ...因为autorelease池已经耗尽?
- ...因为ARC在“释放”调用stringWith ...的alloc + init?
那么让我们来看看调用如何“工作”
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 *错误变量,并使其全局。这要求我在** errorPointer上将属性从__autoreleasing更改为__strong。但是,显然这种修复并不理想,特别是考虑到可能在操作队列中多次重复使用NSInvocations。它也只有kinda证实我怀疑*错误正在被解除分配。
作为一个最终的WTF,我试着玩了一下__bridge casts,但是1.我不确定这是我在这里所需要的,2.在排除后我找不到一个能工作的人。
我喜欢一些洞察力,可能会帮助我更好地理解为什么这一切都不是点击。
Gotcha!所以真的,我应该宣布NSError *错误为'__strong静态NSError *错误'(使它成为一个堆变量?这是甚至合理的说法?我认为堆栈/堆是一个实现细节...) – edelaney05 2012-04-04 01:23:42
啊, ,不要让它变成静态的。只是不要让'NSInvocation'成为你的类的公共部分。该错误只是因为你可以在停止使用后访问它。如果您*必须*,请将错误变量作为您班级的一个属性。 (顺便说一下,堆栈与堆不是实现细节,它们是基本的对象生命周期概念。) – 2012-04-04 01:39:56
错误参数仅在*调用被触发后有用*。您可能会在计时器或操作队列中附加调用,所以错误变量几乎肯定会超出范围。但是,它为什么崩溃的总体要点现在已经很清楚了 - 谢谢! – edelaney05 2012-04-04 02:40:25