I've spent some serious time debugging this problem and I am stumped.
I'm trying to pack custom objects tightly into NSData
to save space when they are saved to disk. I have implemented custom encoding and decoding methods for these custom objects. When I pack and unpack the objects, everything is initialized properly according to the debugger. Immediately after the code runs, it has either an EXC_BAD_ACCESS code=1
or code=2
.
Here is the encoding and decoding:
-(instancetype) initWithData:(NSData *)data {
self = [super init];
if (self) {
_memConstants = [[DWMemoryConstants alloc]init];
int arraySize = _memConstants.chunkSize * _memConstants.chunkSize;
NSData* unencodedBlocks[arraySize];
[data getBytes:unencodedBlocks];
_blocks = [[NSMutableDictionary alloc]init];
for (int i = 0; i < 10; i++) {
DWBlock *block = [[DWBlock alloc]initWithData:unencodedBlocks[i]];
[_blocks setObject:block forKey:[NSValue valueWithCGPoint:CGPointFromString(block.blockName)]];
if (i == 0) {
_position = CGPointFromString(block.chunkName);
}
}
_chunkName = NSStringFromCGPoint(_position);
_bounds = CGRectMake(_position.x * _memConstants.chunkSize * kBlockSpriteWidth,
_position.y * _memConstants.chunkSize * kBlockSpriteWidth,
_memConstants.chunkSize * kBlockSpriteWidth,
_memConstants.chunkSize * kBlockSpriteWidth);
}
return self;
}
-(NSData *) customEncode {
NSMutableData *data = [[NSMutableData alloc]init];
NSData* blocks[_blocks.allValues.count];
int count = 0;
for (DWBlock *block in _blocks.allValues) {
blocks[count] = [block customEncode];
count++;
}
[data appendBytes:&blocks length:sizeof(blocks)];
return data;
}
The exception happens at the end of this code. It successfully prints all of the log messages first, and I have verified that there is nothing wrong with the Chunk object:
-(void) testEncodeDecode {
DWChunk *testChunk = [[DWChunk alloc]initWithPosition:CGPointMake(100, 100)];
NSData *testData = [testChunk customEncode];
NSLog(@"datasize= %@", testData);
DWChunk *unencodedChunk = [[DWChunk alloc]initWithData:testData];
NSLog(@"blocks=%@", unencodedChunk.blocks.allValues);
NSLog(@"this message will print");
}
As I said, all of the variables for the Chunk and Block objects are properly initialized. I suspect that the problem is related to improper releasing of objects, but I don't know where to go from here.
Edit: I have enabled checking for zombies, and now I get this error message:
2014-10-24 11:38:31.775 DigWorld[10622:60b] *** -[NSConcreteMutableData release]: message sent to deallocated instance 0x81790710
EDIT:
My initial thoughts where technically incorrect (read the code too fast...), but the actual error is along the exact same lines. By saving blocks
, you save only the array of pointers, and not the actual data. You can make sure of that: in your comment you say that sizeof(blocks)=3600
and _blocks.allValues.count=900
, so you only save the 900 pointers (* 4 bytes/pointer = 3600 bytes)
The actual data as created by [block customEncode]
is then disposed of by ARC.
When reading this array afterward, you get pointers to pieces of memory (formerly NSData
) that have been marked as freed, producing your crash, and explaining the error message when enabling zombies.
The tricky part: since this is the same instance of the program, and encode and decode are run next to each other, the actual memory has not been reallocated/overwritten. So you get the impression the objects are correctly saved. But rest assured this memory will eventually be overwritten by something else. You can make sure of that by saving the NSData
on disk and decoding it on a re-run of the program.
It seems to me that you are trying to reinvent the wheel here.
The NSCoding
protocol has been created exactly for that purpose: persisting graphs of objects on disk. The NSKeyedArchiver
class will give you serialisation to disk with flexibility, and platform-independance, in a well-tested and documented interface.
You should be leveraging that architecture. Even if you decide that NSKeyedArchiver
creates archives too "big" for your purposes, use the architecture in place and create your own archiver.
If you choose to do so, you can get inspiration with this open source NSCoder
subclass implementation (disclaimer: I wrote it)