Skip to content

Replace/remove the DeallocationObserver mechanism #185

Closed
@dgelessus

Description

@dgelessus

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 ObjCInstances that have custom Python attributes attached. That way only ObjCInstances 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 ObjCInstances never have custom Python attributes added, so in many cases this would avoid the creation of a DeallocationObserver entirely.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew features, or improvements to existing features.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions