From d6755ad9185614bb86c15aa348e6255d40c93764 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 12 Nov 2024 10:49:11 +0800 Subject: [PATCH] Force cache eviction when instance classes don't match. --- changes/539.bugfix.rst | 1 + src/rubicon/objc/api.py | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 changes/539.bugfix.rst diff --git a/changes/539.bugfix.rst b/changes/539.bugfix.rst new file mode 100644 index 00000000..19e16ded --- /dev/null +++ b/changes/539.bugfix.rst @@ -0,0 +1 @@ +An edge case of the instance cache has been fixed. This edge case would cause collection types (such as NSMutableDictionary) to *not* have their Python helper wrappers applied. diff --git a/src/rubicon/objc/api.py b/src/rubicon/objc/api.py index 96104a29..1969936f 100644 --- a/src/rubicon/objc/api.py +++ b/src/rubicon/objc/api.py @@ -657,7 +657,6 @@ def type_for_objcclass(objcclass): This method is mainly intended for internal use by Rubicon, but is exposed in the public API for completeness. """ - if isinstance(objcclass, ObjCClass): objcclass = objcclass.ptr @@ -893,15 +892,27 @@ class or a metaclass, an instance of :class:`ObjCClass` or # it's the correct class. # # Refs #249. + # + # We also need to evict the cache when the instance *class* + # doesn't match. This can happen when an ObjCInstance subclass + # (like ObjCMutableDictionary) has been used - an ObjCInstance + # will respond to the same selectors as ObjCMutableDictionary, + # but it won't have the helper methods (like __setitem__) that + # make the wrapper useful. + # + # Refs #539. + # if cls == ObjCInstance or isinstance(cls, ObjCInstance): cached_class_name = cached.objc_class.name - current_class_name = libobjc.class_getName( - libobjc.object_getClass(object_ptr) - ).decode("utf-8") + current_class = libobjc.object_getClass(object_ptr) + current_class_name = libobjc.class_getName(current_class).decode( + "utf-8" + ) + current_type = type_for_objcclass(current_class) if ( - current_class_name != cached_class_name - and not current_class_name.endswith(f"_{cached_class_name}") - ): + type(cached) is not current_type + or current_class_name != cached_class_name + ) and not current_class_name.endswith(f"_{cached_class_name}"): # There has been a cache hit, but the object is a # different class, treat this as a cache miss. We don't # *just* look for an *exact* class name match, because