In my non-ARC iOS project, I have a method that returns archived data:
- (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];
Since initForWritingWithMutableData:
is deprecated, I modified the implementation:
- (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;
At first, I called autorelease
on the encoded NSData
object before returning it but that resulted in a bad memory access (EXC_BAD_ACCESS
). Everything seems to work fine without autorelease
.
I am now confused. Since I release the archiver before returning the data, I thought autorelease
would protect the NSData
object, deallocating it not before it is processed by the calling method. I worried that the NSData
object might be deallocated right after the archiver is released without the autorelease
call. Somehow, the opposite happens when I run the code.
Could anybody please shed some light on this behavior? Also, if I'm doing something wrong, I'd like to know how to fix the code.
I'm aware of the static method archivedDataWithRootObject:requiringSecureCoding:error:
but I can't easily use it because I don't have a root object as I encode objects individually. Using a root object would break compatibility for existing users of the app (if I understand it correctly).
NSData* data = [archiver encodedData];
The method -encodedData
does not begin with alloc
or new
, nor does it include the word copy
, nor it it called retain
. That means you did not take ownership of it. You must not call release
on it. It doesn't belong to you. Said another way: you have not put a retain on this object; you must not call release on it.
You then release archiver
, which was responsible for data
. There is no promise that data
exists anymore. The fix for this is to retain it before releasing archiver
:
NSData* data = [[archiver encodedData] retain];
This is the effectively the same thing the original code did by initializing data
using +[NSMutableData alloc]
. Note the word "alloc." This gives ownership (a retain) to data
.
By the same naming conventions, this method promises to return an object that the caller doesn't need to release. (It's often easiest to think of this as having a net retain count of 0, but there are many cases where that isn't strictly true.)
- (NSData*) archivedData { ... }
You have put a retain on the data
, so you need to balance that out. You want the object to survive long enough to be returned, however. The fix for that is -autorelease
, just like in the original code:
return [data autorelease];
So all together:
- (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
}
I can't find Apple's helpful Memory Management Rules page anymore, but it's summarized in Three Magic Words.