CFTree Is Leaking It’s Children

It’s 12:40 AM, and I’ve got a client related deadline tomorrow afternoon – so what am I doing writing a blog post? The real answer is: I’m not really sure; but the more relevant answer is: Because this took far too long to track down, and I’d like to save someone else the time I wasted. Essentially it boils down to the fact that the documentation for CFTree is not only misleading, but it seems to be flat out wrong.

First, you may be sitting there wondering what the hell a CFTree is, and several weeks ago I would have wondered the same thing. CFTree is (as it’s name suggests) a tree structure; it is a pseudo-collection *, and is used to organize elements in hierarchical relationships. In this case, the lines between the contents of the collection and the collection itself are blurred. A tree can having relationships with other trees (potentially parents and children), and can hold a payload the size of a pointer, which allows you to store integers, pointers to structs, or objects – even other collections if you feel being all wild and crazy.

This post isn’t supposed to be about all the fancy things you can do with a CFTree, but rather, this line in the documentation for CFTree:

Releasing a tree releases its child trees, and all of their child trees (recursively). Note also that the final release of a tree (when its retain count decreases to zero) causes all of its child trees, and all of their child trees (recursively), to be destroyed, regardless of their retain counts.

The way I interpret that suggests I would have created and (completely) destroyed a tree if I were to do the following: Create Root Tree, Create Child Tree, Append Child Tree, Release Child Tree, Release Tree. Destroying the root tree does not release or destroy it’s children trees. It’s been difficult to track down for a variety of reasons, but I suspected something was wrong (even if I only found one google result on the topic), and I set out to prove it. Lets look at some code.

There’s some very non-cocoa looking stuff going on here, but the details are something I’d like to dive into in a later post. The important thing is that we’ve created a tree with a release callback pointing to DummyReleaseCallback. This is the function that the CFTree is going to call when it is done with the value passed in to treeContext.info, which is that payload I was talking about before. It’s safe to think of this whole process in the same manner you would an NSArray sending the release message to an object when it it removed from the collection.

static void DummyReleaseCallback(const void *info )
{
	NSLog(@"release %i", (int)info);
}

...

{
	CFTreeContext treeContext;
	treeContext.version = 0;
	treeContext.retain = NULL;
	treeContext.release = DummyReleaseCallback;
	treeContext.copyDescription = NULL;
	treeContext.info = (void *)1;
	
	// DummyReleaseCallback should be called after we release this tree
	CFTreeRef dummyTree = CFTreeCreate(NULL, &treeContext);
	CFRelease(dummyTree);

	...

2011-09-07 01:13:26.836 CFTree[1044:f203] release 1

Perfect, the release callback is being called when the tree is released. Let’s add some children…


	...

	treeContext.info = (void *)2;
	CFTreeRef root = CFTreeCreate(NULL, &treeContext);

	for (NSUInteger i = 0; i < 10; i++) {
		CFTreeContext treeContext;
		treeContext.version = 0;
		treeContext.retain = NULL;
		treeContext.release = DummyReleaseCallback;
		treeContext.copyDescription = NULL;
		treeContext.info = (void *)100 + i;
		
		CFTreeRef newChild = CFTreeCreate(NULL, &treeContext);
		CFTreeAppendChild(root, newChild);
		CFRelease(newChild);
	}
	CFRelease(root);
}
2011-09-07 01:14:09.875 CFTree[1044:f203] release 2

This is where things go wrong; we only see one release log. How about if we remove the children with CFTreeRemoveAllChildren before releasing the root?

2011-09-07 01:19:37.394 CFTree[1122:f203] release 100
2011-09-07 01:19:37.395 CFTree[1122:f203] release 101
2011-09-07 01:19:37.395 CFTree[1122:f203] release 102
2011-09-07 01:19:37.395 CFTree[1122:f203] release 103
2011-09-07 01:19:37.396 CFTree[1122:f203] release 104
2011-09-07 01:19:37.396 CFTree[1122:f203] release 105
2011-09-07 01:19:37.397 CFTree[1122:f203] release 106
2011-09-07 01:19:37.397 CFTree[1122:f203] release 107
2011-09-07 01:19:37.397 CFTree[1122:f203] release 108
2011-09-07 01:19:37.398 CFTree[1122:f203] release 109
2011-09-07 01:19:37.398 CFTree[1122:f203] release 2

That’s a lot more like it! Now – these logs really only tell us what’s happening with the info payload of the trees, not the CFTrees themselves. For the sake of comfort, I decided to take a look at object allocations in Instruments. Based on the screenshots below, it is confirmed that destroying only the root tree isn’t sufficient for destroying the entire tree – you can see our 10 children tree’s lingering around.

What To Do

I’ve filed a bug report with Apple, you should too. In the mean time, this hiccup isn’t going to stop me from using CFTree. For now, I’m destroying the tree manually by traversing it (recursively) and using the CFTreeRemoveAllChildren function on each child tree starting with the deepest depth. The part in the documentation about child tree’s being retained by their parent and released when removed is accurate; following normal memory management will result in the expected behavior here. This solution isn’t nearly as clean and pretty as CFRelease(rootTree), but for now it will have to do.

*I say pseudo because it’s not a collection managed by a single object like an NSArray, but rather a collection made up of a relationship of “collection” objects.

About Jerry Jones

Co-Founder of Spaceman Labs, Inc. Formerly of Mellmo, Inc. iOS Developer since 2007. You can find me on LinkedIn, Twitter, and doing backflips on jet skis.
This entry was posted in Code and tagged , , , , . Bookmark the permalink.