在学习关于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]
在阅读了这篇博客文章之后,我意识到需要通过声明和分配地址来处理NSError对象。然而,在这里发生的__autoreleasing和内存管理是有些难以理解的。
**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。但很明显,这种修复方式并不理想,特别是考虑到在操作队列中可能会多次重用NSInvocation。此外,它只是“有点”证实了我的猜测,即*error已被dealloc。
作为最后的困惑,我尝试了一些__bridge转换,但是:1.我不确定这是否是我需要的,2.在排列组合之后我找不到有效的转换方法。
我希望有一些见解能帮助我更好地理解为什么所有这些东西都不能奏效。
__strong static NSError *error
(使它成为堆变量?这样说合理吗?我认为栈/堆是实现细节...) - edelaney05NSInvocation
作为你的类的公共部分。这个错误之所以出现是因为在它不再有用之后仍然可以访问它。如果你一定要这样做,那就把错误变量设为你的类的属性。(顺便说一下,堆栈和堆不是实现细节,它们是基本的对象生命周期概念。) - John CalsbeekNSInvocation
的包装器,那么让一个错误变量与其一起运行就有意义了,并且这将解决生命周期问题。我撤回我的反对。 - John Calsbeek