05 May 12

Use NSMutableData with NSCoder if you will move the data or die

I recently implemented dynamic loading-unloading of audio buffers so I could do real time effects/recording with unlimited lengths on my app. (The core data stuff wouldn’t cut it).

It was working well, except that my save functions got all screwed up and ended up sounding all glitchy and out of order like some kind of accidental fennesz. That’s pretty useful and was almost as fun as the time I discovered Yasunao Tone’s Wounded Man stuff but I prefer to have save working.
I was using NSData to encode via encodeDataObject for my NSCoder. The thing is, as you see in the code sample below, I am dealing with large amounts of data >100 MB, so I am loading and unloading the data before and after the NSData creation and it’s encodeDataObject call. I thought this would be fine since I expect the encoder to write immediately. Instead, it appears it does not write immediately, and there is no flush method to force it to write the data, meaning that if you modify the data in NSData even after it is out of scope, it will end up writing something other than you intended.

If you use NSMutableData, however, it writes immediately.
I understand that NSData is immutable. However, since I was creating with dataWithBytesNoCopy: I was using my own data and thought it would finish by the end of encodeDataObject.


- (void)encodeWithCoder:(NSCoder *)encoder
   [encoder encodeInt:channels forKey:@"channels"];
   [encoder encodeInt:numFrames forKey:@"frames"];
   [encoder encodeFloat:averagePower forKey:@"power"];
   // encodeArrayOfObjCType takes into account sizeof(SInt16) via the type parameter, so we don't computer         
   // actual memory size, but the number of SInt16's in the array                                                  
   size_t bufsize = channels * numFrames *sizeof(*buffer);
   // use NSData to encode but do not free the buffer when it releases                                             
   [self fetchBuffer];
   NSData *data = [NSMutableData dataWithBytesNoCopy:buffer length:bufsize freeWhenDone:NO];
   [encoder encodeDataObject:data];
   [self unfetchBuffer];