omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    Access PyObject From ObjCClass

    Pythonista
    objcutil objc
    2
    7
    2679
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • ?
      A Former User last edited by

      Hello, I'm playing with creating classes with the objc_util module, and I am running into a problem. I want a class that contains objc methods that can access the underlying python object. For example,

      class TestClass(object):
      	def __init__(self):
      		self.objcInstance = objc_util.create_objc_class(
      			"TestClass",
      			methods = [self.testMethod]
      		).alloc().init()
      
      		self.testVariable = 1
      		pass
      
      	def testMethod(self, _self, _cmd):
      		print(self.testVariable)
      		pass
      
      TestClass().objcInstance.testMethod()
      

      But when I run this i get the following error,

      Traceback (most recent call last):
        File "/private/var/mobile/Containers/Shared/AppGroup/08CF4A94-F824-408A-B12B-53EA02F43C24/Pythonista3/Documents/Projects/musicruntime/queueviewer.py", line 48, in <module>
          TestClass().objcInstance.testMethod()
        File "/private/var/mobile/Containers/Shared/AppGroup/08CF4A94-F824-408A-B12B-53EA02F43C24/Pythonista3/Documents/Projects/musicruntime/queueviewer.py", line 39, in __init__
          methods = [self.testMethod]
        File "/var/containers/Bundle/Application/E768493A-4B9B-48EB-82D1-FADC51CC89B6/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 1191, in create_objc_class
          _add_method(method, class_ptr, superclass, basename, protocols)
        File "/var/containers/Bundle/Application/E768493A-4B9B-48EB-82D1-FADC51CC89B6/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 1153, in _add_method
          raise ValueError('%s has %i arguments (expected %i)' % (method, len(argspec.args), len(argtypes)))
      ValueError: <bound method TestClass.testMethod of <__main__.TestClass object at 0x1180f0f28>> has 3 arguments (expected 2)
      

      The only I can think to solve this would be either creating a global variable or turning TestClass into a singleton, but that isn't ideal.

      Thanks for any help!.

      mikael 2 Replies Last reply Reply Quote 0
      • mikael
        mikael @Guest last edited by

        @Hw16279180, this way works, but not sure if it is moving towards your goal. If you need to, you can use metaclass to hide the instance creation within a more Pythonic class. I can share some code on that later today.

        
        import types
        
        import objc_util
        
        TestClass = objc_util.create_objc_class("TestClass")
        
        def test_method(self):
            print(self.test_variable)
        
        def create_test_instance():
            test_instance = TestClass.alloc().init()
            setattr(test_instance, 'test_method', types.MethodType(test_method, test_instance))
            return test_instance
            
        if __name__ == '__main__':
            
            instance = create_test_instance()
            instance.test_variable = 'Instance attribute'
            instance.test_method()
        
        1 Reply Last reply Reply Quote 0
        • mikael
          mikael @Guest last edited by

          @Hw16279180, with this helper:

          import inspect
          import types
          import uuid
          
          import objc_util
          
          
          class ObjCPlus:
              
              def __new__(cls, *args, **kwargs):
                  objc_class = getattr(cls, '_objc_class', None)
                  if objc_class is None:
                      objc_class_name = 'TempClass'+str(uuid.uuid4())[-12:]
                      objc_methods = [value
                          for value in cls.__dict__.values()
                          if (
                              callable(value) and 
                              '_self' in inspect.signature(value).parameters
                          )
                      ]
                      objc_protocols = getattr(cls, '_objc_protocols', [])
                      if not type(objc_protocols) is list:
                          objc_protocols = [objc_protocols]
                      objc_class = objc_util.create_objc_class(
                          objc_class_name,
                          methods=objc_methods,
                          protocols=objc_protocols
                      )
                  
                  instance = objc_class.alloc().init()
          
                  for key in dir(cls):
                      value = getattr(cls, key)
                      if inspect.isfunction(value):
                          if not '_self' in inspect.signature(value).parameters:
                              setattr(instance, key, types.MethodType(value, instance))
                          if key == '__init__':
                              value(instance, *args, **kwargs)
                  return instance
          

          ... your original example becomes:

          class TestClass(ObjCPlus):
              
              def __init__(self):
                  self.test_variable = 'Instance attribute'
              
          instance = TestClass()
          assert instance.test_variable == 'Instance attribute'
          assert type(instance) is objc_util.ObjCInstance
          

          A more realistic example combining different types of methods could be:

          class GestureHandler(ObjCPlus):
              
              # Can be a single string or a list
              _objc_protocols = 'UIGestureRecognizerDelegate'
              
              # Vanilla Python __init__
              def __init__(self):
                  self.other_recognizers = []
              
              # ObjC delegate method matching the protocol
              def gestureRecognizer_shouldRecognizeSimultaneouslyWithGestureRecognizer_(
                      _self, _sel, _gr, _other_gr):
                  self = ObjCInstance(_self)
                  other_gr = ObjCInstance(_other_gr)
                  return other_gr in self.other_recognizers
                  
              # Custom ObjC action target for gesture recognizers
              def gestureAction(_self, _cmd):
                  self = ObjCInstance(_self)
                  ...
                  
              # Vanilla Python method
              @objc_util.on_main_thread
              def before(self, other):
                  return other.recognizer in self.other_recognizers
          
          ? 1 Reply Last reply Reply Quote 1
          • ?
            A Former User @mikael last edited by

            @mikael Wow! this is amazing. one question though, what is this line for?
            objc_class = getattr(cls, '_objc_class', None)

            mikael 2 Replies Last reply Reply Quote 0
            • mikael
              mikael @Guest last edited by

              @Hw16279180, if you want to build the ObjC class ”by hand” with create_objc_class, you can set that as the base class with the _objc_class class variable. Thus the previous example would become:

              def gestureRecognizer_shouldRecognizeSimultaneouslyWithGestureRecognizer_(
                      _self, _sel, _gr, _other_gr):
                  self = ObjCInstance(_self)
                  other_gr = ObjCInstance(_other_gr)
                  return other_gr in self.other_recognizers
                  
              # Custom ObjC action target
              def gestureAction(_self, _cmd):
                  self = ObjCInstance(_self)
                  ...
                  
              GestureHandlerObjC = objc_util.create_objc_class(
                  'GestureHandlerObjC',
                  methods=[
                      gestureAction,
                      gestureRecognizer_shouldRecognizeSimultaneouslyWithGestureRecognizer_,
                  ],
                  protocols=['UIGestureRecognizerDelegate'],
              )
              
              class GestureHandler2(ObjCPlus):
                  
                  _objc_class = GestureHandlerObjC
                  
                  # Vanilla Python __init__
                  def __init__(self):
                      self.other_recognizers = []
                      
                  # Vanilla Python method
                  @objc_util.on_main_thread
                  def before(self):
                      return self.other_recognizers
              
              1 Reply Last reply Reply Quote 0
              • mikael
                mikael @Guest last edited by

                @Hw16279180, if you are interested in ObjC bridging, you could also google rubicon-objc and all the nice descriptor/typing stuff ”our very own” @dgelessus and others have done there.

                ? 1 Reply Last reply Reply Quote 0
                • ?
                  A Former User @mikael last edited by

                  @mikael Thanks I'll look into it.

                  1 Reply Last reply Reply Quote 0
                  • First post
                    Last post
                  Powered by NodeBB Forums | Contributors