Giter Club home page Giter Club logo

Comments (7)

dgelessus avatar dgelessus commented on June 4, 2024 1

As usual, your observation is correct. :) It's a consequence of how custom Objective-C subclasses are handled. Currently, you need to override the Objective-C init method instead of the Python __init__ method:

class CantRoller(UIViewController):
    @objc_method
    def init(self):
        self = ObjCInstance(rubicon.objc.send_super(self, 'init'))
        # Initialization code goes here.
        # Note that self is an ObjCInstance, not a normal Python object.
        # You can only call Objective-C methods and set Objective-C properties on self.
        # It's not possible to set normal Python attributes on self, at least not reliably.
        # The Python attributes might get lost in some cases.
        return self

There are a few reasons for this behavior:

  1. All Objective-C objects are represented by an ObjCInstance object on the Python side, no matter what their class is in Objective-C. (ObjCClass and ObjCMetaClass are special cases, but that doesn't matter here.) That means that an Objective-C CantRoller object is represented by a Python ObjCInstance object, not a Python CantRoller object. And of course, CantRoller.__init__ is never called on something that isn't a CantRoller.
  2. The __new__ and __init__ methods of ObjCInstance are called every time an ObjCInstance is created for an Objective-C object. (This happens for example when a function/method/property returns an Objective-C object, or when you make an ObjCInstance from a pointer by hand.) This means that __new__ and __init__ are often called more than once for each Objective-C object. This means that even if an Objective-C CantRoller object was a Python CantRoller object, the __init__ method would be called at random times, and not when the object is created.

It might be possible to add a special case for this, to add a default init method to custom Objective-C subclasses, which automatically calls the __init__ method of the class. I'm not sure how useful that would be though. The only difference is the method name, you still wouldn't be able to set Python attributes on self correctly.

from rubicon-objc.

freakboy3742 avatar freakboy3742 commented on June 4, 2024 1

I'm going to close this ticket; it's not obvious to me that there's a specific action that needs to be taken. If you think there's a feature lurking in this ticket that is worth addressing, let us know and we can make that more explicit (either by reopening this ticket, or opening a new ticket)

from rubicon-objc.

dimaqq avatar dimaqq commented on June 4, 2024

What about other methods?

Let's say I had:

class Whatever:

    def helper(self):
        self.foo = self.bar

    @objc_method
    def exported_(self, somearg):  # called from ObjC domain
        self.helper()

Is it possible or at present not recommended?

How to refer to Python's self correctly?

from rubicon-objc.

dgelessus avatar dgelessus commented on June 4, 2024

In an objc_method, there is no Python self as you'd expect. The self you get is actually an ObjCInstance, so it only has the Objective-C methods. For example:

>>> class Whatever(rubicon.objc.NSObject):
...     def py_method(self):
...         print("Hello from py_method!", self)
...     @rubicon.objc.objc_method
...     def objcMethod(self):
...         print("Hello from objc_method!", self)
...         
>>> o = Whatever.alloc().init()
>>> o.objcMethod()
Hello from objc_method! <Whatever: 0x1019a0af0>
>>> o.py_method()
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2881, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-9-52bd697d1711>", line 1, in <module>
    o.py_method()
  File "/Users/Shared/_Zeug/Python/projects/rubicon-objc/rubicon/objc/objc.py", line 1292, in __getattr__
    raise AttributeError('%s.%s %s has no attribute %s' % (type(self).__module__, type(self).__qualname__, self.objc_class.name, name))
AttributeError: rubicon.objc.objc.ObjCInstance Whatever has no attribute py_method

There's no way to call py_method, because py_method actually doesn't exist anymore:

>>> Whatever.py_method
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2881, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-14-fcb043b9ed85>", line 1, in <module>
    Whatever.py_method
  File "/Users/Shared/_Zeug/Python/projects/rubicon-objc/rubicon/objc/objc.py", line 1292, in __getattr__
    raise AttributeError('%s.%s %s has no attribute %s' % (type(self).__module__, type(self).__qualname__, self.objc_class.name, name))
AttributeError: rubicon.objc.objc.ObjCClass Whatever has no attribute py_method

This happens because when the Whatever class was created, it was converted from a Python class to an Objective-C class. In the process, all objc_methods were registered on the Objective-C side. After that, the class only exists in Objective-C, and all Python attributes are gone. Now you can only access things that were registered in Objective-C before (methods, class methods, properties, etc.).

In short, anything you write in a custom Objective-C subclass has to be an objc_method (or objc_classmethod, or objc_property, etc.) or it gets lost. This is certainly not a good solution - ideally, there should either be an error if you have non-ObjC things in your ObjC subclass, or the Python objects need to be stored somehow.

from rubicon-objc.

dimaqq avatar dimaqq commented on June 4, 2024

Okay, or the time being I'll just use a closure.

For the future though, I may need multiple instances of Python-defined Obj-C class.

If I use closure there too, then Obj-C class will be defined multiple times, is that:

  • forbidden?
  • allowed, but leaks memory?
  • ok?

Perhaps a better approach is to define a py property, of a suitable type that gets automatically represented as whatever Python object it happens to be, i.e. composition? Is there some boiler-plate for this?

from rubicon-objc.

dgelessus avatar dgelessus commented on June 4, 2024

It's not possible to define two Objective-C classes with the same name. If you try, you'll get an error. You can define an Objective-C class inside a function, but that will fail if you call the function more than once.

You can of course add an ivar to you class that holds a pointer to a Python object. But if you do that, you have to prevent the Python object from being garbage-collected. That means you either need to keep a reference to the object elsewhere in Python, or use the Python C API to increase the refcount in init and decrease it in dealloc.

from rubicon-objc.

dimaqq avatar dimaqq commented on June 4, 2024

Looks like any practical program needs a mechanism to keep Python reference in Objective-C space.

The inverse is .retain(), that's straightforward enough, a "reference holder" class is easy to write. There's still an open question of reference cycles, but that's inevitable, right?

The workaround is a module global, but that's kinda... impractical, as ultimately a program would end up with 2 parallel trees, one of Objective-C objects, another of Python objects.

from rubicon-objc.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.