Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the content of NSData separately reference-counted?

I have a large malloc'd region that I want to wrap in an NSData object. Some time later, I make a copy of that NSData object. I want the two NSData objects to live independent lifetimes. ARC takes care of ref-counting the NSData objects themselves, but I'm trying to clarify the lifetime of the contained malloc'd region. Here's a code sketch:

float* cubeData = (float*)malloc(cubeDataSize);
printf("cubeData=%p\n", cubeData);
// cubeData=0x01beef00

for (...) { /* fill the cubeData array */ }

NSData* data = [NSData dataWithBytesNoCopy:cubeData length:cubeDataSize
  freeWhenDone:YES];

NSData* data2 = [data copyWithZone:nil]

printf("data.bytes=%p data2.bytes=%p\n", data.bytes, data2.bytes);
// data.bytes=0x01beef00 data2.bytes=0x01beef00

It's OK with me that copyWithZone doesn't deep-copy the malloc'd region — I can use [NSData dataWithData:] if I want a deep copy. What isn't clear to me (and I'm not sure how best to test) is which NSData object owns the underlying malloc'd buffer? If they both hold a reference to the malloc'd buffer (using some form of opaque reference counting) that's great! But if the malloc'd buffer gets freed when the data object is released (as implied by freeWhenDone:YES), data2 will have trouble on its hands.

Can someone explain what NSData does in this case? Alternatively, can someone suggest a definitive test to prove to myself what's going on?

like image 647
Tim Ruddick Avatar asked Nov 12 '22 01:11

Tim Ruddick


1 Answers

To the underlying question:

Is the content of NSData separately reference-counted?

No. (But looking at your code, it shouldn't matter. See below after this diversion.)

--- Start Diversion ---

ARC manages retains and releases on Objective-C objects by sending the equivalent of retain and release messages at appropriate times. "Appropriate times" are determined at compile time by code inspection. That is exactly everything it does. When you start creating pointers to non-object pieces of those objects (i.e. bytes), you're on your own to manage lifetime.

@CouchDeveloper provides good information about objc_precise_lifetime. Placing this attribute on data objects can protect you from an ARC optimizations when dealing with internal pointers, but it isn't really relevant here. The point of objc_precise_lifetime is to tell ARC it's not allowed to release an object before the referencing variable goes out of scope. The problem it solves looks like this:

NSData *data = ...;
void *stuff = data.bytes; // (1)
doSomething(stuff); // (2)

ARC has an optimization that says it's allowed to destroy data between line (1) and line (2), since you never reference data again, even though data is in scope. Adding the objc_precise_lifetime attribute forbids that optimization. When you start using NSData a lot, this attribute can become important.

--- End Diversion ---

OK, but what about your situation?

float* cubeData = (float*)malloc(cubeDataSize);
NSData* data = [NSData dataWithBytesNoCopy:cubeData length:cubeDataSize freeWhenDone:YES];
NSData* data2 = [data copyWithZone:nil]

After this code has run, there are two possibilities (and you're not supposed to care most of the time which is true, since these are immutable objects):

  • data and data2 are both strong pointers to the same NSData object. That object owns the malloced memory and will free it when deallocated. (This is the one that's almost certain to happen in this particular case, but that's an implementation detail.)
  • data points to an NSData object that owns the malloced memory and will free it when deallocated. data2 points to a different NSData object with its own memory (which it will free when it is deallocated.)

(There are other options; perhaps NSData uses an underlying dispatch_data or a copy-on-write scheme. But all the options should effectively look like the above from the outside.)

In the first case, if data goes out of scope, but data2 is still around, then the owning NSData is preserved. No problem. In the second case, when data goes out of scope, it destroys its memory, but data2 has an independent copy of it, so again no problem.

I think your confusion is coming from thinking that data owns the memory. It doesn't. The NSData object that data points to owns the memory. data and data2 are just pointers.

like image 81
Rob Napier Avatar answered Nov 15 '22 07:11

Rob Napier