Description
Currently, every Objective-C object that is wrapped in ObjCInstance
by Rubicon has a DeallocationObserver
object associated with it. DeallocationObserver
is a custom Objective-C class defined by Rubicon, which is used to notify Rubicon when the Objective-C object it's attached to is deallocated. When that happens, the object's ObjCInstance
is removed from the global ObjCInstance._cached_objects
cache.
This mechanism has two main disadvantages:
- It requires creating a new Objective-C object from Python whenever an Objective-C object is wrapped in
ObjCInstance
for the first time. This has a noticeable performance impact when creating many Objective-C objects from Python - see Speed up instantiating new class instances #183. - It uses the Objective-C runtime's "associated objects" mechanism. There's nothing directly wrong with this - it's a public, documented, mature, non-deprecated API - but it is a relatively obscure feature of the runtime, and general advice is that it should be considered a last-resort solution.
- Also, the GNUStep Objective-C runtime doesn't support associated objects. GNUStep support isn't a very high priority and would also require some other changes, but the fact that Rubicon's core makes heavy use of associated objects is actually one of the major blockers for GNUStep compatibility - see Add support for GNUStep #176.
The question is how we could replace this mechanism. I don't know of any better way to implement the exact behavior that DeallocationObserver
currently provides, i. e. notifying Rubicon when an Objective-C object is deallocated. But there are other ways to implement what we use this mechanism for, namely removing no longer needed entries from the ObjCInstance
cache. For example, we could replace the cache dictionary with a weakref.WeakValueDictionary
, so that entries would automatically disappear as soon as the ObjCInstance
in question is deallocated on the Python side.
A consequence of using a weak dictionary as the cache would be that it's no longer guaranteed that wrapping the same Objective-C object will always return the same ObjCInstance
. It's only guaranteed that if an ObjCInstance
exists for an Objective-C object, wrapping the same object again will return the same ObjCInstance
. But once the last reference to that ObjCInstance
(outside the cache) is deleted, the ObjCInstance
is probably deallocated, so wrapping the same Objective-C object again in the future will probably return a new ObjCInstance
.
This could be a problem if the ObjCInstance
had Python attributes attached to it by the user, because they would be lost when the ObjCInstance
is destroyed and recreated from only the Objective-C object information. I don't know how common this case is, i. e. that an ObjCInstance
has Python objects attached to it, has no references from the Python side, but the code expects the Python attributes to still be there when the Objective-C object is wrapped in an ObjCInstance
again in the future.
A possible solution for that might be to make the ObjCInstance
cache weak, but keep an additional collection of strong references to ObjCInstance
s that have custom Python attributes attached. That way only ObjCInstance
s with custom Python attributes need to have a DeallocationObserver
, to remove them from the strong reference collection once the corresponding Objective-C object is deallocated. This would allow creating the DeallocationObserver
only once it's actually needed, i. e. when the first custom Python attribute is added, instead of immediately when the object is first wrapped. Most ObjCInstance
s never have custom Python attributes added, so in many cases this would avoid the creation of a DeallocationObserver
entirely.