specifically, the code example from @JonB, which made the ui.NavigationView actually useful by allowing access to the view stack. Unfortunately, I've run into serious crashiness. I get a segmentation fault quite easily by calling the functions which provide access to the root or top view. And I've traced it back to the calls to pyObject() which are used to turn the ObjCInstance's back into the ui python objects we need.

Try on_main_thread... That's what I used in the "more robust" version in that thread.

Also, I seem to recall some issues with restype=py_object -- try using a c_void_p restype, and see if you getna crash, or if pyObject ever returns a null pointer.
It is possible to cast a c_void_p to py_object, possibly using from_address (I recall some acrobatics)

@JonB I actually am decorating all the calls which access the internals with @on_main_thread, just took that straight from your "robust" example...still crashes after one or two attempts. I'll try using the c_void_p thing.

@JonB well now that's interesting...I mean, I am calling objcinstance.pyObject(...) to get access to what I thought was a reference to the actual python view object. That is done in a method, and then returned to the caller, and that caller is holding onto that reference, and during this time, the view is still in existence, so I don't know why that python object would suddenly become invalid.

As in the simple example I gave in the initial post: the crash happens just from calling pyObject() more than a couple times on the same view. Whether I hold a reference or not, whether the view object still exists or not.

Run the above, uncommenting the crashing one to see it crash Pythonista.
So, the first test gets the pyObject of a local reference to a globally held view (it doesn't appear to matter if the view is presented or not). It also does the same thing to a locally created and held view.

The crashing test is the same scenario as in my simplest example: in a loop, create and access the pyObject of a locally created view.

The second test produces an interesting result: multiple calls to a function which attempts to access the pyObject of a global reference to a view. This one doesn't crash, but it does produce a traceback:

And sure enough, if I print out what p and v are in that second test, they suddenly become weakrefs a couple iterations in. And then a few more iterations in, they suddenly lose the ability to get the objc_instance. The question is why? How does it suddenly change from a _ui.View to a weakref (with the same memory location)? Why does it continue to work for a few more iterations?

The first test is accessing a global view which is definitely held to the end. But it also accesses a local view created within the function, but that view is also technically held until the end of the function.
The crashing test creates views inside a loop inside a function, which ought to work the same as the first test.
The interesting test starts to make it clear: the only difference between it and the first test is that it accesses the global view, not a local reference to it. And that global is becoming a weakref at some point.

This must be related to what happens with @on_main_thread and the execution of the code in the interpreter thread. It's getting into some thick weeds with the inner workings of things...and I think this gets me enough info to make it work for my purposes.

I vageuly recall some issue a long time ago, where the view objcinstance might not be guaranteed to be completely initialized until it is presented.

my guess is that the interaction between python to objc and back in a tight loop (python creates a view, which somewhere the needs to go create a the actual objc view, then calling pyObject i think probably has c code calling back into python code, which screws with the GIL) and somewhere things are just not quite thread safe.

using c_void_p, however seems to mostly resolve the issue. the check for None is important, and you can retry. the code below doesnt complain at all for me, though i have an old, slow device. if i remove on_main_thread, it complains that pyobj returned None, but the second retry always works.

@JonB well, that cracks it then, definitely something in the deep woods of the interaction between the objective-c runtime and the python interpreter. I'm going to try grafting the retry technique above to the NavigationView wrapper to see if it will make it stable.

@mikael, @JonB I wish I had better news, but it seems that no matter whether I attempt ctypes.py_object, ctypes.c_void_p with a cast, multiple retries, on_main_thread, etc. I always eventually crash. So I am going to give up for now...the solution I have now is less efficient, but stable, and works well for my purposes.

It's quite a bit harder to debug than the simple tests demonstrated throughout this thread. I can create tests which crash, and ones which don't by switching around the order of things and where objects are created. In the actual code, there is a ui.NavigationView, which hosts views with ui.TableView children, and those tables have data sources which respond via their action callbacks by eventually calling the code which tries to get the pyObject of the current navigation view and then push or pop a view in response. So where the issue really arises is somewhere in the complex interaction of ui thread and main thread.

I am using this approach to develop a more pythonic way to use create_objc_class -- using a python class to encapsulate the objc class, with method decorators that replace the |_self, _sel ` arguments with a regular self (python object) argument, which is automatically looked up from the objc object.

You can store whatever python data you want using associated objects, just change the key (which should be a selector, of your choosing).

in either case, things become unstable and eventually crashes occur, especially after trying it again. The py_object seems to crash immediately, while the c_void_p way just makes things highly unstable. Oddly, getting the value via c_void_p gives me back the actual ui.View instance, not a c_void_p object which I need to cast(), which is what I was expecting...which to me is a sign of something fishy going on. It never appears to be None, and since it's coming in as an actual ui.View instance and not ctypes.c_void_p, there's no "value" property to check.

I'll look at the objc_addAssociatedObject() idea, though at this point that seems about the same as just keeping a mapping entirely in python of the id of the objc view to the python ui.View object, since I'm already going to the trouble of tracking things manually.

I am using this approach to develop a more pythonic way to use create_objc_class -- using a python class to encapsulate the objc class, with method decorators that replace the |_self, _sel ` arguments with a regular self (python object) argument, which is automatically looked up from the objc object.

@JonB regardless of the troubles I was having...that objc decorator idea is brilliant, and ought to be rolled into objc_util.

re: my problems with pyObject being crashy. After going over the way my code was executing, I rearranged things to be absolutely certain that all attempts to access internals via pyObject() and cast() were within a @on_main_thread...and that seemed to finally make it stable. So, though I'm not entirely clear where it was going wrong, somewhere at least one of those calls was being made outside the main thread, and was therefore making the whole thing unstable at any future call.