Traceback using Gestures module
Wanted to add simple swipe-gesture recognition to a custom View I was writing, so I thought I'd try out the utterly fantastic Gestures module by @mikael, which seemed like it had exactly what I needed...
...and it worked! ...the first time I run my code. The second time, and any time thereafter, until I force-quit Pythonista and restart, it gives the following traceback when detecting my gesture:
Traceback (most recent call last): File "_ctypes/callbacks.c", line 315, in 'calling callback function' File "/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents.../Gestures.py", line 154, in gestureRecognizer_shouldRequireFailureOfGestureRecognizer_ return self.objc_should_require_failure(self.fail_other, gr, other_gr) File "/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents.../Gestures.py", line 157, in objc_should_require_failure_default return simplify(func, gr, other_gr) File "/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents/.../Gestures.py", line 133, in simplify gr_o = ObjCInstance(gr) TypeError: 'NoneType' object is not callable
My project is python2.7, and Gestures is really python3 code, though the only incompatibility I found was this import of a python3-only module which isn't even used in the Gestures code:
from types import SimpleNamespace
So I commented that line out and it worked fine...until this issue. To reiterate: I can run my code once, load the pyui, my custom View shows up, and reacts to the swipe as expected. Then I exit my script, and run it again, and the second time around it gives this traceback...it seemed like it was holding a reference to the gesture recognizer instance from one run to the next, so I tried both:
class MyView(ui.View): def did_load(self): import Gestures Gestures.Gestures(retain_global_reference=True).add_swipe(self, self._handleSwipe, direction=[Gestures.Gestures.LEFT, Gestures.Gestures.RIGHT])
class MyView(ui.View): def did_load(self): import Gestures self._gr = Gestures.Gestures(retain_global_reference=False) self._gr.add_swipe(self, self._handleSwipe, direction=[Gestures.Gestures.LEFT, Gestures.Gestures.RIGHT])
Thinking maybe it was the "retain_global_reference" which was causing this...but either way I get the same result.
Anyone ever see this before? Any ideas?
@shinyformica, I think the issue is related to the way I have used closure to access
selfin the ObjC delegate functions. Maybe we could find another way to connect to the Python instance.
@mikael yeah, that's definitely the reason, in some fashion. The Objective-C side of things is definitely holding tightly to the Gestures object instance that "self" refers to at the time the delegate ObjCClass is defined. I had to modify it even further to get it to work from one run of the script to another (without killing and restarting Pythonista in between) because it was still holding the reference to the first instance of my custom View class that created the ObjCClass from one run to the next, and thus passing the wrong gesture recognizer instances to the simultaneous gesture and fail/fail_other calls.
Not knowing much about how this works under the hood, I don't know how to prevent it, but I'll look into it a bit more...perhaps it would be possible to hold or pass the reference to the Gestures object as data, and use some generic way to call the appropriate method on that object, instead of holding the method instance.
@shinyformica, latest version on Github now uses a class-level lookup to find the right instance when calling delegate functions. Could you please give it a try?
@mikael just got to try this this morning. Unfortunately, while it seems like it would work, I once again encounter the strange issue of module-level things becoming undefined when the script is run a second time, from way back at the start of this thread. In this case it is the actual Gestures class which is suddenly None when the code tries to call Gestures.get_self(cls, key).
Again, this only happens the second time the script runs, and every time thereafter. First time through, things work as expected. I actually managed a solution of my own, which isn't ideal: I append the id of the instance of the Gestures object to the name of the delegate class being created by the call to objc_util.create_objc_class() so each Gestures instance gets a unique delegate class and instance of that class, which will be sure to have self as a reference to that particular Gestures instance. This appears to solve the problem, though it means I'll have a new ObjC class definition for each Gesture instance, which in my case means one for each control that gets instantiated...and I don't know the performance or memory implications of that.
I really wish I knew why I was having that strange "undefinition" issue, since your solution only creates one class, and registers the instances of the Gesture object, it seems much more efficient. I'll see if I can figure out a way to do something similar to your way of finding a reference to the Gestures instance without holding an explicit self reference, without triggering my other problem.
@mikael a little more fiddling around, and looking at how other people have used objc_util and ObjCInstance's, it seems I can just stash a reference to the Gestures object instance for my control in the actual delegate instance created for that control:
self._delegate = PythonistaGestureDelegate.new() self._delegate._gestures = self
and then I can safely access that reference in the objc callbacks:
def gestureRecognizer_shouldRecognizeSimultaneouslyWithGestureRecognizer_(_self, _sel, gr, other_gr): import objc_util delegate_instance = objc_util.ObjCInstance(_self) gestures_instance = delegate_instance._gestures return gestures_instance.objc_should_recognize_simultaneously(self.recognize_simultaneously, gr, other_gr)
so now only one actual delegate class definition is created in objective-c, and the delegate instances for each control are all separately able to access the Gestures instance they use. I'll have to make sure I break the cyclic reference between the gesture delegate instance and the Gestures instance, but I can do that via a weakref or explicitly removing the cycle when I get rid of the control.
@shinyformica, thanks, I will implement this. Does this option suffer from the vanishing Gestures class?
I will also check this with an iPad.
@mikael thankfully it does not suffer from that strange vanishing-definitions thing. Though I am now locally importing objc_util anyplace I try to use ObjCInstance() or ObjCClass(), which appears to prevent it from showing up and causing trouble.
I tested the above technique using weakref to hold the reference to the Gestures object and it worked fine, so that will prevent a cyclic-reference:
self._delegate = PythonistaGestureDelegate.new() import weakref self._delegate._gestures = weakref.ref(self)
hey, have you copies objc_util and made edits? objc_util should actually never be cleared after it is loaded, so unless you are manually del'ing sys.modules['objc_util'] it should not be getting cleared out from under you. But, if you had a shadow copy on your path, it would.
import objc_util print(objc_util.__file__)
i have an older version of gestures, but i also could not reproduce your issues
@shinyformica, thanks. I will include the weakref version.
I was also wondering if you had some globals-clearing code somewhere. Remembering some previous threads where the goal was to make Pythonista act like a fresh interpreter starting every time you run a script.