I have class Item and class List (which has an NSMutableArray).
Every time class Item is instantiated (and destroyed) it posts a notification, which is listened-to by class List. When class List receives the notification is adds the instance of class Item to its list.
I’m trying to have class Item also post a notification that its about to be dealloc’d. The problem is that class List’s NSMutableArray retains the instance of class Item.
What’s the most appropriate means of handling this situation? If I decrement the count when adding it to List’s array, then an exception will be thrown when class List attempts to call removeObject (since it’ll try to dealloc the object.)
Basically, I want a “monitor” class List that contains a list of all “live” instances of Item. But, I also need the ability to release/dealloc the instances and have them report they’re being dealloc’d so List can remove them from its NSMutableArray.
Thanks for your help.
If I understand correctly, you want an array that maintains weak references to its items, as opposed to strong references?
I don’t know of a way to do this with anything “built-in” in Cocoa. The only way I’d know of to do this is to make the array yourself, and have the storage be__weak id[]. That would automatically zero-out the place in the array when the object deallocates. If you’re under the retain-release model, you could use something likeMAZeroingWeakRefto get the same behavior.This is definitely an interesting question, and I don’t know of an easier answer. I’d love to be proven wrong!
Ha, I love being wrong!
There’s a class called
NSPointerArraythat looks like it can do what you’re looking for. However, it’s only available on the Mac, and it only auto-zeros when you’re using garbage collection.I’ll keep thinking about this. This is an interesting problem! 🙂
So I kept thinking about this, and came up with a solution. It uses two unconventional things:
NSMutableArray(egads!)For the first bit, I had to to subclass
NSMutableArrayso that I could inject some custom logic intoaddObject:(and related methods). I didn’t want to do this via swizzling, sinceNSArrayand friends are a class cluster, and swizzling into/out of clusters is fraught with peril. So, a subclass. This is fine, but we’re going to lose some of the awesome features we get from “pure”NSArrayinstances, like how they do weird things when they get big. Oh well, such is life.As for the second bit, I needed a way for any arbitrary object to notify that it is about to or just finished deallocating. I thought of dynamically subclassing the object’s class, injecting my own
dealloc/finalizemethod, callingsuper, and then smashing theisaof the object, but that just seemed a little too crazy.So, I decided to take advantage of a fun little thing called associated objects. These are to ivars what categories are to classes: they allow you to dynamically add and remove pseudo-instance variables at runtime. They also have the awesome side effect of getting automatically cleaned up with the object deallocates. So what I did is just created a little throw away object that posts a notification when it is deallocated, and then attached it to the regular object. That way when the regular object is deallocated, the throw away object will be as well, resulting in a notification being posted, which I then listen for in the
NSMutableArraysubclass. The notification contains a (stale) pointer to the object that is in the process of getting destroyed, but since I only care about the pointer and not the object, that’s OK.The upshot of all of this is that you can do:
The source is on github, and it should (theoretically) work just as well on iOS as Mac OS X (regardless of GC mode): https://github.com/davedelong/Demos
Cheers!
… and I just thought of a way to do this without a custom subclass, but I’m tired and will post the updated answer tomorrow.
the next morning…
I’ve just updated the project on Github with an
NSMutableArraycategory that allows you to create a trueNSMutableArraythat auto-zeroes its objects as they’re deallocated. The trick was to create aCFMutableArrayRefwith a customretaincallback that sets up the proper observation, and then just cast thatCFMutableArrayRefto anNSMutableArrayand use that (ah, the magic of Toll-Free Bridging).This means you can now do:
I added a
typedefto define these asNSAutozeroingMutableArray, just to make it explicitly clear that while this is anNSMutableArray, it doesn’tretainits objects like a normalNSMutableArray. However, since it’s just atypedefand not a subclass, you can use them interchangeably.