在非 ARC 代码中从 NSKeyedArchiver 中提取数据

Extracting Data from NSKeyedArchiver in Non-ARC Code

在我的非 ARC iOS 项目中,我有一个 returns 归档数据的方法:

- (NSData*) archivedData {
    NSMutableData* data = [[NSMutableData alloc] init];
    NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    // Encode the fields that must be archived:
    [archiver encodeObject:... forKey:...];
    ...
    [archiver finishEncoding];
    [archiver release];
    return [data autorelease];

由于 initForWritingWithMutableData: 已弃用,我修改了实现:

- (NSData*) archivedData {
    NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
    // Encode the fields that must be archived:
    [archiver encodeObject:... forKey:...];
    ...
    [archiver finishEncoding];
    NSData* data = [archiver encodedData];
    [archiver release];
    return data;

起初,我在返回编码的 NSData 对象之前调用了 autorelease,但这导致了错误的内存访问 (EXC_BAD_ACCESS)。没有 autorelease.

似乎一切正常

我现在很困惑。由于我在返回数据之前释放了存档器,我认为 autorelease 会保护 NSData 对象,在调用方法处理它之前不会释放它。我担心 NSData 对象可能会在没有 autorelease 调用的情况下释放归档程序后立即被释放。不知何故,当我 运行 代码时会发生相反的情况。

有人可以解释一下这种行为吗? 另外,如果我做错了什么,我想知道如何修复代码。

我知道静态方法 archivedDataWithRootObject:requiringSecureCoding:error: 但我不能轻易使用它,因为我没有根对象,因为我单独对对象进行编码。使用根对象会破坏应用程序现有用户的兼容性(如果我理解正确的话)。

NSData* data = [archiver encodedData];

方法 -encodedData 不以 allocnew 开头,也不包含单词 copy,也不调用 retain。这意味着您没有获得它的所有权。您不得在其上调用 release。它不属于你。换句话说:你没有保留这个对象;你不能在上面调用 release。

然后您释放 archiver,它负责 datadata 不再存在。解决方法是在发布之前保留它 archiver:

NSData* data = [[archiver encodedData] retain];

这实际上与原始代码通过使用 +[NSMutableData alloc] 初始化 data 所做的相同。请注意“分配”一词。这将所有权(保留)赋予 data.

根据相同的命名约定,此方法承诺 return 调用者不需要释放的对象。 (通常最容易将其视为净保留计数为 0,但在很多情况下并非如此。)

- (NSData*) archivedData { ... }

您在 data 上设置了保留,因此您需要对其进行平衡。但是,您希望该对象存活足够长的时间以便 returned。修复是 -autorelease,就像在原始代码中一样:

return [data autorelease];

总之:

- (NSData*) archivedData {
    NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
    // Encode the fields that must be archived:
    [archiver encodeObject:... forKey:...];
    ...
    [archiver finishEncoding];
    NSData* data = [[archiver encodedData] retain]; // Retain here
    [archiver release];
    archiver = nil; // It's good practice to nil values that are no longer valid
    return [data autorelease]; // autorelease here
}

我找不到 Apple 有用的内存管理规则页面,但在 Three Magic Words 中进行了总结。