Getting Python ui widget from objc instance?
I'm certain this has been asked before, probably even by me...but searches didn't come up with anything quickly:
Is it possible to get the pythonista ui widget object back from the objc instance object?
So, for example, if I'm in a python function which is called from objective-c, and therefore only has access to the ObjCInstance of the widget, is there a way to retrieve the python widget object?
My initial sense is no, it isn't possible. ui.View objects are not weakreference-able, so I can't stash a weakref in the objc instance pointing back at the python instance, and I'm not sure if storing a strong reference to the python object would cause a nasty garbage-collection-breaking loop.
@shinyformica, I have just stored a direct reference to the python object, or earlier also maintained a lookup dict for this purpose. Then taking some care to clean up at the end.
@mikael did you notice any GC issues when storing a strong ref to the python object in the objc instance?
I don't know how things are hooked up underneath...whether the fact that the python widget object has an objc_instance member, which holds the objc object, which would then in turn have a member pointing back at the python object creates an un-breakable reference loop that would prevent GC, and thus potentially cause a memory leak.
pyObject() works, but you have to manually set the restype/argtypes. If the view is visible, I think you are guaranteed that it has not been gc'd, though any callbacks need to be careful to import what it needs explicitly within the callback, since module level imports may have been cleared. Also, callbacks should not use any globals.
@JonB oh...interesting. So would that just be:
pyObject.restype = c_void_p pyObject.argtypes = 
That thread was yours ;)
Make sure you are doing it on_main_thread, and for whatever reason a c_void_p seems more robust than py_object restype.
@JonB ...you know what's most embarrassing here? That thread you pointed to is MY OWN DANGED THREAD!
Apparently I already encountered and was shown the solution to this issue with pyObject()...just forgot.
@shinyformica, @JonB, just took another look at Gestures, which uses the mind-boggling? combo of
weakrefs, but seems to work ok for most people. Is there a clear advantage to the pyobject approach that would recommend refactoring Gestures?
@mikael, the weakref to the gestures instance I think was a suggestion I made a while back to make sure the delegate methods access the correct python object, but not prevent it from being GC'd. I don't think you can use pyObject in that case, because the delegate is an objc class you created, not a ui.View.
In the slightly altered version of your Gestures module I use, I turned off the retain global bit, and also replaced the somewhat heavyweight use of a ui.Button instance as a mechanism for capturing the gesture with a small objc class with just the handler function.
@shinyformica, ok, thanks.
There is the option of turning retain_global off, if you want to take over the responsibility of making sure that Gestures sticks around. Or did you have some clever way of not needing to worry about that?
Also, a PR or some lines of code for the handler function would be welcome.
@mikael in my use case, I'm actually making a Gestures instance per-custom-widget. Since each of my widgets can have 2 or 3 gestures, it is simpler to just let each one have its own Gestures instance and install the various recognizers on each one. Gestures isn't particularly heavyweight, especially without the internal ui.Button instance, and then I don't need any global tracking of who has gestures installed, since when the widget goes away, it takes the gestures instance with it.
Creating a little handler method in the gesture delegate class, which can be attached to a gesture recognizer is as simple as:
...existing gesture delegate definition... def handleGesture_(_self, _cmd, recognizer): import objc_util delegate = objc_util.ObjCInstance(_self) if not delegate: return recognizer = objc_util.ObjCInstance(recognizer) if not recognizer: return gestures = delegate._gestures() if not gestures: return gestures._handleGestureRecognizer(recognizer) methods = [... handleGesture_]
Then, where you make the recognizers, instead of the whole button action thing, with all the associated bookkeeping:
recognizer = \ objc_util.ObjCClass(recognizer_type).alloc() recognizer.initWithTarget_action_(self._delegate, objc_util.sel("handleGesture:")).autorelease() view.objc_instance.addGestureRecognizer_(recognizer)