Giter Club home page Giter Club logo

rubicon-objc's Introduction

logo

Rubicon-ObjC

Python Versions Project Version Project status License Build Status Discord server

Rubicon-ObjC is a bridge between Objective-C and Python. It enables you to:

  • Use Python to instantiate objects defined in Objective-C,
  • Use Python to invoke methods on objects defined in Objective-C, and
  • Subclass and extend Objective-C classes in Python.

It also includes wrappers of the some key data types from the Foundation framework (e.g., NSString).

Tutorial

Want to jump in and get started? We have a hands-on tutorial for beginners.

How-to guides

Looking for guidance on how to solve a specific problems? We have how-to guides and recipes for common problems and tasks, including how to contribute.

Reference

Just want the raw technical details? Here's our Technical reference.

Background

Looking for explanations and discussion of key topics and concepts? Our background guides may help.

Community

Rubicon is part of the BeeWare suite. You can talk to the community through:

Code of Conduct

The BeeWare community has a strict Code of Conduct. All users and developers are expected to adhere to this code.

If you have any concerns about this code of conduct, or you wish to report a violation of this code, please contact the project founder Russell Keith- Magee.

Contributing

If you experience problems with this backend, log them on GitHub. If you want to contribute code, please fork the code and submit a pull request.

rubicon-objc's People

Contributors

brutusthebee avatar cclauss avatar cculianu avatar conniepocky avatar danyeaw avatar dayof avatar dependabot[bot] avatar dgelessus avatar freakboy3742 avatar glasnt avatar jacebrowning avatar jeamland avatar landrybr avatar longhanks avatar mhsmith avatar ojii avatar qqfunc avatar rmartin16 avatar ryan-work avatar sethmlarson avatar stantonxu avatar therealphildini avatar uranusjr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rubicon-objc's Issues

How to terminate the terminate Async && "rubicon.objc.async" to "rubicon.objc.eventloop"

The code for the second example within the Section of Integrating asyncio with CoreFoundation is breaking:

   # Import the Event Loop Policy and lifecycle
   from rubicon.objc.async import EventLoopPolicy, CocoaLifecycle

   # Install the event loop policy
   asyncio.set_event_loop_policy(EventLoopPolicy())

   # Get a handle to the shared NSApplication
   from ctypes import cdll, util
   from rubicon.objc import ObjCClass

   appkit = cdll.LoadLibrary(util.find_library('AppKit'))
   NSApplication = ObjCClass('NSApplication')
   app = NSApplication.sharedApplication()

   # Get an event loop, and run it, using the NSApplication!
   loop = asyncio.get_event_loop()
   loop.run_forever(lifecycle=CocoaLifecycle(app))

So far I have refactored it to be:

    # Import the Event Loop Policy and lifecycle
    from rubicon.objc.eventloop import EventLoopPolicy, CocoaLifecycle
    import asyncio
    # Install the event loop policy
    asyncio.set_event_loop_policy(EventLoopPolicy())

    # Get a handle to the shared NSApplication
    from ctypes import cdll, util
    from rubicon.objc import ObjCClass

    appkit = cdll.LoadLibrary(util.find_library('AppKit'))
    NSApplication = ObjCClass('NSApplication')
    app = NSApplication

    # Get an event loop, and run it, using the NSApplication!
    loop = asyncio.get_event_loop()
    loop.run_forever(CocoaLifecycle(app))
    CocoaLifecycle(app).stop()

Here is the typical program output when the stop or terminate fails:

    Traceback (most recent call last):
    File "aio/r1a.py", line 17, in <module>
    loop.run_forever(CocoaLifecycle(app))
    File "/Users/ryancarl/.local/share/virtualenvs/RubIO-gfdP6ba6/lib/python3.7/site- 
    packages/rubicon/objc/eventloop.py", line 444, in run_forever
    self.stop()
    File "/Users/ryancarl/.local/share/virtualenvs/RubIO-gfdP6ba6/lib/python3.7/site- 
    packages/rubicon/objc/eventloop.py", line 524, in stop
    self._lifecycle.stop()
    File "/Users/ryancarl/.local/share/virtualenvs/RubIO-gfdP6ba6/lib/python3.7/site- 
    packages/rubicon/objc/eventloop.py", line 638, in stop
    self._application.terminate()
    File "/Users/ryancarl/.local/share/virtualenvs/RubIO-gfdP6ba6/lib/python3.7/site- 
    packages/rubicon/objc/api.py", line 591, in __getattr__
    type(self).__module__, type(self).__qualname__, self.objc_class.name, name)
    AttributeError: rubicon.objc.api.ObjCClass NSApplication has no attribute terminate

I have tried many attempts to stop the async action but still no luck... anyone have any thoughts or suggestions?

Thanks in advance!

"rubicon.objc.async" to "rubicon.objc.eventloop" is this correct?

When running the first example on the "Integrating asyncio with CoreFoundation" I noticed that the code:

    # Import the Event Loop Policy
    from rubicon.objc.async import EventLoopPolicy
    # Install the event loop policy
    asyncio.set_event_loop_policy(EventLoopPolicy())
    loop = asyncio.get_event_loop()
    # Get an event loop, and run it!
    loop.run_forever()

: is not working.

To get the example to work I needed to modify the code to:

    # Import the Event Loop Policy
    from rubicon.objc.eventloop import EventLoopPolicy
    import asyncio
    # Install the event loop policy
    asyncio.set_event_loop_policy(EventLoopPolicy())

    # Get an event loop, and run it!
    loop = asyncio.get_event_loop()
    loop.run_forever()

Is this correct?

Documentation

We need some sort of proper documentation. At the moment we have the README, the CHANGELOG, and a few docstrings in the source code. There's also https://rubicon-objc.readthedocs.io/, which seems to be an outdated copy of the README - once we have proper docs, we should probably put them there.

I'm honestly not very familiar with documentation tools for Python. As far as I know, Sphinx is more or less the standard system for Python documentation (it's used for the Python stdlib, Numpy and Sympy for example, and is what readthedocs uses apparently), so that would be a natural choice. One disadvantage is that (as far as I can tell) docstrings in the Python source code are completely separate from the documentation source, so the two would need to be maintained separately. (I don't think "no docstrings at all" is a good option.)

To be clear, I'm up for writing some docs myself, I just have no idea what tools one would use for the job.

Needs documentation on how to pass an pointer-to-pointer “out parameter”

It is common in Cocoa API for a method to have an “out parameter”, e.g. an NSError ** parameter that is used to return an error when a call fails.

Taking the deserialisation method in NSJSONSerialization for example, this method

+ (id)JSONObjectWithData:(NSData *)data
                 options:(NSJSONReadingOptions)opt
                   error:(NSError **)error

can be called like this in Objective-C:

NSError *err = nil;
id obj = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];

And the equivalent with Rubicon-objc would be

from ctypes import byref, c_void_p

err = c_void_p(0)
obj = NSJSONSerialization.JSONObjectWithData_options_error_(
    data, 0, byref(err),
)

NB: err = None will not work since byref expects a ctypes instance, and does not automatically convert None to a NULL pointer.

How to get __init__ called?

I've noticed that when a class exporting an objc_method is instantiated, __init__() is not called on the instance.

First of all, am I correct?

Then, is it on purpose or a limitation?

Or am I just doing it wrong?

Here's sample code:

    # some objc_method
        self.cant = CantRoller.new()
        coll.setDataSource_(cant)

class CantRoller(UIViewController):
    def __init__(self):
        logging.warn("never called")

    @objc_method
    def collectionView_numberOfItemsInSection_(self, view, section: int) -> int:
        return 16

What's the canonical solution?

pypy support

It seems like this might be able to pick up where NSPython left off, and provide a functional bridging API for PyPy. Given that ctypes is already supported on pypy I'm not sure if there's much to do besides just add it to tox?

Odd strings in traceback

Whenever I mess something up, I get normal traceback KeyError: xxx followed by cryptic traceback that includes odd/unprintable characters, like UHâ and �ÏdYˇ�'

Traceback (most recent call last):
  File ".../python3.5/site-packages/rubicon/objc/objc.py", line 1313, in __getattr__
    return self.__dict__[name]
KeyError: 'addSubView'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../python3.5/site-packages/ui/__main__.py", line 66, in application_didFinishLaunchingWithOptions_
    ObjCClass("UIApplication").sharedApplication().keyWindow.addSubView(view)
  File ".../python3.5/site-packages/rubicon/objc/objc.py", line 1316, in __getattr__
    raise AttributeError('ObjCInstance UHâÂAWAVATSIâ˜Iâ˛L;= has no attribute �ÏdYˇ�'  self.__dict__['objc_class'].name, name))
AttributeError: ObjCInstance UIWindow has no attribute addSubView

Could this be a sign of e.g. stale pointer being read, or rubicon trying to decode something that's not really decodable?

(Here, site-packages/ui is my code)

Proposal for nicer init()

Through trial and error I got following to pass:

tmp = ObjCClass("UINavigationController").alloc().initWithRootViewController_(root)

Wouldn't it be nice to get that data from call signature:

tmp = ObjCClass("UINavigationController").alloc().init(rootViewController=root)

And then, perhaps, move to:

UINavigationController = ObjCClass("UINavigationController")
tmp = UINavigationController(rootViewController=root)

Calling print() on "self created" Obj-C class raises exception.

Passing a "self created" Obj-C class to the print() function raises an exception because the attributes name and description attribute are missing.

class CameraDelegate(UIViewController, protocols=[AVCaptureMetadataOutputObjectsDelegate]):
    pass

class Camera(Widget):
    def create(self):
        self.delegate = CameraDelegate.alloc().init()
        print(self.delegate)  # <--- line of the error

Console Output:

2018-07-23 15:10:04.588666+0200 qrscanner[1434:490451]     print(self.delegate)
2018-07-23 15:10:04.588728+0200 qrscanner[1434:490451]   File "/var/containers/Bundle/Application/3922AEA9-79D7-4594-AE16-AE32B44FBD9C/qrscanner.app/Library/Application Support/com.example.QRScanner/app_packages/rubicon/objc/api.py", line 530, in __str__
2018-07-23 15:10:04.589198+0200 qrscanner[1434:490451]     raise ValueError('{self.name}.description returned nil'.format(self=self))
2018-07-23 15:10:04.589289+0200 qrscanner[1434:490451]   File "/var/containers/Bundle/Application/3922AEA9-79D7-4594-AE16-AE32B44FBD9C/qrscanner.app/Library/Application Support/com.example.QRScanner/app_packages/rubicon/objc/api.py", line 591, in __getattr__
2018-07-23 15:10:04.589680+0200 qrscanner[1434:490451]     type(self).__module__, type(self).__qualname__, self.objc_class.name, name)
2018-07-23 15:10:04.590064+0200 qrscanner[1434:490451] AttributeError: rubicon.objc.api.ObjCInstance CameraDelegate has no attribute name```

My current fix is to add the name and description attributes by hand.

self.delegate.name = 'CameraDelegate'
self.delegate.description = 'CameraDelegate description'
print(self.delegate) # <-- No error this time.

Blocks wrapping Python callables should keep the callable alive

I'm getting bugginess with objc blocks passed as parameters. I noticed rubicon has support for them automagically as such:


def blockTes1t() -> None:
    # ....

# .... and then maybe do:
vc.dismissViewControllerAnimated_completion_(True,blockTest1)

According to #54 , this should work. However I get a runtime error half the time:

SystemError: null argument to internal routine

.. and the other half of the time I get a random crash and a debugger-level stack trace -- indicating to me something is not quite right in the Python/ObjC glue code layer. :)

No matter how hard I try I can't consistently get this to work at all. Am I doing something wrong? Does the Block code for rubicon-objc need to be updated? I'm willing to go in and update it but I just want to make sure it's not ME doing something wrong (some guidance is appreciated!)

Also, definitely a feature is missing: I can't call methods that expect Blocks as parameters but accept 'nil' blocks because the type 'None' for blocks should be accepted, but the current Block code doesn't accept it (some methods in ObjC do indeed accept nil blocks). If I pass 'None' for a block parameter, I get this error:


    arg = Block(arg).block
  File "/Users/calin/Library/Developer/CoreSimulator/Devices/80F9328C-18C7-43C1-9E93-F4CB128AD3CA/data/Containers/Bundle/Application/27B85C08-FEFC-45C5-9D30-E192F20DA04C/Electron-Cash.app/Library/Application Support/com.c3-soft.ElectronCash/app_packages/rubicon/objc/runtime.py", line 2299, in __init__
    raise TypeError('Blocks must be callable')
TypeError: Blocks must be callable

Protocol support

We should support working with Objective-C protocols.

  • There should be an ObjCProtocol class to look up protocols by name, similar to ObjCClass.
  • ObjCProtocol instances should provide basic properties like name and list of superprotocols.
  • Protocol conformance should be checkable using isinstance/issubclass (#68).
  • Objective-C subclasses created in Python should be able to conform to protocols (by adding them to the supertype list).
    • This should check that all required methods and properties are implemented by the subclass. (Maybe the runtime does this already, but since it only communicates errors as boolean return values, we should provide better error messages ourselves.)
    • This could be used to infer method signatures. That is, when the subclass includes a method with the same name as one in a protocol (or superclass), and the method implementation has no parameter/return annotations, the types should be inherited from the superclass/protocol.
  • Users should be able to create Objective-C protocols from Python using class syntax.
    • How should method declarations look? Regular Python method syntax with type annotations, and method bodies are ignored?

Not really an issue — we’re using rubicon-objc for upcoming Electron Cash for iOS

Hi guys,

One of the other guys on our team was happy when he heard the stories of how great it was working with you guys to solve some minor issues that were present in rubicon/objc (namely the Blocks not working right as well as crucial ‘send_super’ lexical type issues), and he suggested I tell you what we’re doing with rubicon in case you wanted to know.

Anyway not sure the best way to contact you guys so I’m just posting this here.

Feel free to close it.

I just wanted to thank you for creating rubicon-objc. Not only is it very elegant as a lib, but its small footprint and great devs made it a pleasure to use and troubleshoot.

Just using it and working with dgelessus on the minor issues we found was an education. I learned a great deal both about python and about objective c internals.

Anyway not sure if you are interested or if you guys keep an”protects using our software” page or anything, but I wanted to tell you what we use rubicon-objc for (as well as Briefcase).

We ported the Electron Cash wallet (a Bitcoin Cash SPV wallet) over to iOS. Since the brunt of the core code was written in Python, we decided we needed a glue layer from Python that speaks ObjC so we could implement the UI.

Briefcase and rubicon-objc came to the rescue!

Briefcase provided an already-built setup with libpython.. and rubicon provided the bridging layer to ObjC.

Both are great tools and we’re happy to have them.

Anyway — here’s our project website: http://www.electroncash.org

We’re open source and we are planning on releasing our iOS port very soon. We’re still beta testing and haven’t officially announced it yet.

Anyway thank you and best regards!

Unable to run example asyncio code

I was attempting to port over some code built on PyObjC (specifically: rumps) to instead use rubicon-objc because I wanted integrate the event loop of a native GUI app with asyncio. After running into some difficulties, I thought I'd try to port over something even simpler (specifically: https://github.com/half0wl/simon) And when I had issues with that, I tried simply running the example code from the rubicon-objc asyncio how-to, with the addition of one line, "import asyncio":

# Import the Event Loop Policy and lifecycle
from rubicon.objc.eventloop import EventLoopPolicy, CocoaLifecycle

import asyncio

# Install the event loop policy
asyncio.set_event_loop_policy(EventLoopPolicy())

# Get a handle to the shared NSApplication
from ctypes import cdll, util
from rubicon.objc import ObjCClass

appkit = cdll.LoadLibrary(util.find_library('AppKit'))
NSApplication = ObjCClass('NSApplication')
NSApplication.declare_class_property('sharedApplication')
app = NSApplication.sharedApplication

# Get an event loop, and run it, using the NSApplication!
loop = asyncio.get_event_loop()
loop.run_forever(lifecycle=CocoaLifecycle(app))

Running this gives an error that I don't understand:

Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 232, in 'calling callback function'
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/rubicon/objc/eventloop.py", line 197, in _cf_socket_callback
    callback, args = self._writer
TypeError: cannot unpack non-iterable NoneType object

Any suggestions on how to get the how-to code up and running? Am I just missing something? And are there any known working examples I can refer to of open source code using rubicon-objc with asyncio?

Broken link

The Code of Conduct link in the README file is broken. The 4th line from the end:

.. _BeeWare Community Code of Conduct: http://pybee.org/community/behavior/

should probably be:

.. _Code of Conduct: http://pybee.org/community/behavior/

Issue using objc_property

I have an class roughly like this:

class MyObj(NSObject):
    foo = objc_property()

    @objc_method
    def init(self):
        self = ObjCInstance(send_super(self, 'init'))
        self.foo = True
        return self

Later on whenever I want to write to self.foo, I get errors such as:


app_packages/rubicon/objc/runtime.py", line 1382, in __getattr__
    type(self).__module__, type(self).__qualname__, self.objc_class.name, name)
AttributeError: rubicon.objc.runtime.ObjCInstance __NSCFNumber has no attribute isEqualTo_
(lldb) 

I basically can't really overwrite any properties. I suspect there is some bug in runtime.py. Any thoughts?

I'm on rubicon_objc-0.2.10-py3.5

Please help!

Improve error handling when a required selector isn't defined

At present, if a class doesn't implement a required selector, the Objective C runtime raises a hard crash. For example:

2017-12-29 12:09:23.220 python[22410:8656994] -[TogaCanvas window]: unrecognized selector sent to instance 0x7fad6342b1b0
2017-12-29 12:09:23.228 python[22410:8656994] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TogaCanvas window]: unrecognized selector sent to instance 0x7fad6342b1b0'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fffaa5022cb __exceptionPreprocess + 171
	1   libobjc.A.dylib                     0x00007fffbf31b48d objc_exception_throw + 48
	2   CoreFoundation                      0x00007fffaa583f04 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
	3   CoreFoundation                      0x00007fffaa474755 ___forwarding___ + 1061
	4   CoreFoundation                      0x00007fffaa4742a8 _CF_forwarding_prep_0 + 120
	5   AppKit                              0x00007fffa7f4ffcf -[NSView addSubview:] + 59
	6   _ctypes.cpython-35m-darwin.so       0x000000010b9602ef ffi_call_unix64 + 79
	7   ???                                 0x00007fff55020830 0x0 + 140734619584560
)
libc++abi.dylib: terminating with uncaught exception of type NSException

It should be possible to avoid the hard crash by implementing doesNotRecognizeSelector: on the base ObjInstance. This should provide a better opportunity to manage or report the crash than showing a memory dump.

ObjCStrInstance isn't as string-like as the Python standard library sometimes requires

The recent change (#110) to use ObjCStrInstance instead of automated str conversion has introduced some incompatibilities in Toga.

I've found two examples so far, but they're in a similar class of usage:

  • Decimal(s)
  • os.path.dirname(s)

Both fail with an error that the argument isn't a string or bytes. It's easy enough to work around - you just force the conversion with str(). However, given it's a relatively common use case, it would be nice to either (a) provide a workaround that does allows "as str" conversion to be done automatically, or (b) add this to the list of workarounds you're likely to need to use when dealing with objects returned by an ObjC method.

Error during the importation of rubicon.objc

Hi,

I'm trying to use rubicon-obj but I'm not able to compile it.
In a python shell, when I write this command line :
from rubicon.objc import ObjCClass, objc_method
I get this error :

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python/2.7.12/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/rubicon/objc/__init__.py", line 5, in <module>
    from .objc import (
  File "/usr/local/Cellar/python/2.7.12/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/rubicon/objc/objc.py", line 1354
    def dealloc(self, cmd) -> None:
                           ^
SyntaxError: invalid syntax

I did pip install pip install rubicon-objc

Coud you please help me ? Thank
Regards

ctypes_patch introspection fails on iOS apps packaged for distribution

The ctypes_patch introduced in #85 works fine on macOS, iOS Simulator, and iOS devices in developer mode. However, when an iOS app is compiled and bundled for distribution (either App Store or Ad Hoc), the bitcode recompilation process appears to munge things in such a way that the ctypes patch fails.

Reproduction instructions

  • Create an iPhone app that uses Python 3.6 and Rubicon 0.2.10
  • Export the app for Ad Hoc distribution
  • Run the app. It will crash with the following stack trace:
 Traceback (most recent call last):
   File "/var/containers/Bundle/Application/CE92BB63-8128-4885-81ED-6955F3E15984/Travel Tips.app/Library/Application Support/com.keith-magee.traveltips/app/traveltips/__main__.py", line 10, in <module>
     main().main_loop()
   File "/var/containers/Bundle/Application/CE92BB63-8128-4885-81ED-6955F3E15984/Travel Tips.app/Library/Application Support/com.keith-magee.traveltips/app/traveltips/app.py", line 256, in main
     return TravelTips('Travel Tips', 'com.keith-magee.traveltips')
   File "/var/containers/Bundle/Application/CE92BB63-8128-4885-81ED-6955F3E15984/Travel Tips.app/Library/Application Support/com.keith-magee.traveltips/app_packages/toga/app.py", line 54, in __init__
     self.factory = get_platform_factory(factory)
   File "/var/containers/Bundle/Application/CE92BB63-8128-4885-81ED-6955F3E15984/Travel Tips.app/Library/Application Support/com.keith-magee.traveltips/app_packages/toga/platform.py", line 24, in get_platform_factory
     from toga_iOS import factory
   File "/var/containers/Bundle/Application/CE92BB63-8128-4885-81ED-6955F3E15984/Travel Tips.app/Library/Application Support/com.keith-magee.traveltips/app_packages/toga_iOS/factory.py", line 1, in <module>
     from .app import App, MainWindow
   File "/var/containers/Bundle/Application/CE92BB63-8128-4885-81ED-6955F3E15984/Travel Tips.app/Library/Application Support/com.keith-magee.traveltips/app_packages/toga_iOS/app.py", line 3, in <module>
     from rubicon.objc import objc_method
   File "/var/containers/Bundle/Application/CE92BB63-8128-4885-81ED-6955F3E15984/Travel Tips.app/Library/Application Support/com.keith-magee.traveltips/app_packages/rubicon/objc/__init__.py", line 3, in <module>
     from .runtime import (  # noqa: F401
   File "/var/containers/Bundle/Application/CE92BB63-8128-4885-81ED-6955F3E15984/Travel Tips.app/Library/Application Support/com.keith-magee.traveltips/app_packages/rubicon/objc/runtime.py", line 13, in <module>
     from . import ctypes_patch
   File "/var/containers/Bundle/Application/CE92BB63-8128-4885-81ED-6955F3E15984/Travel Tips.app/Library/Application Support/com.keith-magee.traveltips/app_packages/rubicon/objc/ctypes_patch.py", line 112, in <module>
     ctypes.pythonapi.PyType_stgdict.restype = ctypes.POINTER(StgDictObject)
   File "/var/containers/Bundle/Application/CE92BB63-8128-4885-81ED-6955F3E15984/Travel Tips.app/Library/Python/lib/python36.zip/ctypes/__init__.py", line 361, in __getattr__
   File "/var/containers/Bundle/Application/CE92BB63-8128-4885-81ED-6955F3E15984/Travel Tips.app/Library/Python/lib/python36.zip/ctypes/__init__.py", line 366, in __getitem__
   AttributeError: dlsym(RTLD_DEFAULT, PyType_stgdict): symbol not found

This error does not appear if you run the app on an attached debug device. It only occurs during Ad Hoc or App Store distribution.

Helpful hints

When distributing for Ad Hoc or App Store distribution, you only get the contents of NSLog. Output to Stdout and stderr (which is to say all Python output) isn't captured. Therefore, you'll need to capture stdout and stderr to a file, and dump that file on exit.

The failure occurs when running the Python script, so if your app delegate is defined in Python, that delegate won't execute - and as a result, and pipe or GCD-based logging redirection won't activate.

Cannot retrieve key from NSUserDefaults

I am trying to detect from Python if the OS interface is set on Light or Dark mode. I wanted to use the code reported here for Objective C/Swift and here for pyobjc, basically

NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];

So, I installed rubicon-objc and executed the following code:

>>> from rubicon.objc import ObjCClass
>>> NSUserDefaults = ObjCClass("NSUserDefaults")
>>> print(NSUserDefaults.standardUserDefaults.stringForKey('AppleInterfaceStyle'))
None

In contrast, everything works by running the code provided in the pyobjc post:

>>> from Foundation import NSUserDefaults
>>> NSUserDefaults.standardUserDefaults().stringForKey_('AppleInterfaceStyle')
'Dark'

This is the first time I try rubicon-objc, so I assume I am doing something wrong. Can you help me? Thanks.

Remove use of Core Foundation?

Should we eventually remove the use of Core Foundation for operations like creating strings and collections? As far as I can tell, there are no bootstrapping issues where Core Foundation would be needed to access Foundation APIs. For example, NSStrings can be created from bare C data using the +[NSString stringWithUTF8String:] or +[NSString stringWithCharacters:length:] methods.

The advantage of using only Foundation is that we don't need to interface with an additional library. It's not necessary to use Core Foundation directly even with APIs that only accept Core Foundation types, because the corresponding Foundation types can be used instead, using toll-free bridging. Foundation is also more high-level and easy to use - you don't need to declare all the functions and manually keep track of object types, the Objective-C runtime takes care of everything.

Type handling - No restype encoding for...

Hello BeeWare,

Thanks for sharing rubicon-objc. I very much like the light approach at playing with Mac OS APIs with it, instead of PyObjc -- while perfectly confortable with Python, C and generic low-level systems/networking code, I confess myself as a complete Objective-C + Mac OS newbie.

(a bit of context: I've been exploring bluetooth programming on various platforms. I started with Linux, which was surprisingly nice and while waiting for some PRs to be reviewed and hopefully merged, I decided to move on to the Mac; this issue arises from my first very simple attempt).

The facts

Exploring the Apple docs on bluetooth, in particular https://developer.apple.com/reference/iobluetooth/iobluetoothdevice?language=objc, I came up with the following script to enumerate the names and bluetooth addresses of paired devices:

from ctypes import cdll, util
from rubicon.objc import ObjCClass

if __name__ == '__main__':

    cdll.LoadLibrary(util.find_library('IOBluetooth'))
    IOBluetoothDevice = ObjCClass('IOBluetoothDevice')

    paired_devices = IOBluetoothDevice.pairedDevices()
    num_devices = paired_devices.count()
    for i in range(num_devices):
        device = paired_devices.objectAtIndex_(i)
        name = device.name
        address = device.getAddress()
        print('name=%r address=%r' % (name, address))

When run rubicon-objc complains, printing out:

No restype encoding for b'getAddress' (b'r^{BluetoothDeviceAddress=[6C]}')

With this, the names of the devices are printed out nicely, but the addresses show up as None.

Browsing the code, I quickly figured out the culprit in the ObjCMethod class. I went digging and learned a bit about Objective C type encodings (at https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html) and quickly matched the b'r^{BluetoothDeviceAddress=[6C]}' to the following declaration in /System/Library/Frameworks/IOBluetooth.framework/Versions/A/Headers/Bluetooth.h:

typedef struct  BluetoothDeviceAddress          BluetoothDeviceAddress;
struct  BluetoothDeviceAddress
{
        uint8_t         data[ 6 ];
};

(this is under 10.9.5, but per docs should be present in all the more recent OS releases)

The way forward

Ideally, the ObjCMethod class, and in particular ctype_for_encoding would know how to handle arbitrarily complex Objective C types, including structs and arrays (I see there is some special handling for some types in the code, such as for NSPoint, NSSize and friends -- not sure why, but probably because they vary depending on the OS/CPU architecture).

A generic approach would parse an encoding like the one I got -- b'r^{BluetoothDeviceAddress=[6C]}' -- and dynamically create a ctypes Structure derived class such that the rubicon-objc caller would get something workable. At first sight, while not terribly complex, the parsing would need to be more sophisticated so as to handle sequences of encodings like they're specified for structures (more, regarding structures: the field name would be gone, of course, in such automatic approach).

Another, simpler, approach that worked for me was a small hack that at least returned a raw ctypes c_void_p when ctype_for_encoding fails at its task: this at least does not throw away data and allows the calling code (at the callers responsibility / knowledge) to cast that to the appropriate type and access the underlying data (or make a mistake and segfault!). :)

My hack was just replacing the return None right after the print for No restype encoding for... with a return c_void_p. From there, I updated my code to:

from ctypes import cdll, util, Structure, c_ubyte, cast, POINTER
from rubicon.objc import ObjCClass

class BluetoothDeviceAddress(Structure):
    _fields_ = [
        ('data', c_ubyte * 6),
    ]

if __name__ == '__main__':

    cdll.LoadLibrary(util.find_library('IOBluetooth'))
    IOBluetoothDevice = ObjCClass('IOBluetoothDevice')

    paired_devices = IOBluetoothDevice.pairedDevices()
    num_devices = paired_devices.count()
    for i in range(num_devices):
        device = paired_devices.objectAtIndex_(i)
        name = device.name
        address = device.getAddress()
        btda_ptr = cast(address, POINTER(BluetoothDeviceAddress))
        addr_str = ':'.join(map(lambda b: '%x' % b, btda_ptr[0].data))
        print('name=%r address=%r addr_str=%r' % (name, address, addr_str))

This happily did its thing and correctly printed out the address strings of the paired bluetooth devices.

What next

Thanks for taking the time in reading this.
I'd love to hear feedback and guidance on how to move forward and I'll be happy to contribute.

PS: By the way, is there a more Pythonic approach at iterating through the elements of an NSArray? :)

Is inheritance working fully on the Python side?

Can I inherit form a class that inherits from an ObjC object?

What I'm thinking of is the following code:

class MyA(NSObject):
    
    aProp = objc_property()
    
    @objc_method
    def init(self) -> ObjCInstance:
        self = ObjCInstance(send_super(self, 'init'))
        print ("MyA init")
        self.aProp = "MyA Property"
        return self
    
    @objc_method
    def dealloc(self) -> None:
        print ("MyA dealloc")
        send_super(self, 'dealloc')

class MyB(MyA):
    
    
    @objc_method
    def init(self) -> ObjCInstance:
        self = ObjCInstance(send_super(self, 'init'))
        print ("MyB init")
        self.aProp = "MyB Property"
        return self
    
    @objc_method
    def dealloc(self) -> None:
        print ("MyB dealloc")
        send_super(self, 'dealloc')

Then, I should be able to do:

a = MyB.new().autorelease()
print("a prop = %s"%(str(a.aProp)))

If I do that, I get the following error and crash at runtime:

Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 234, in 'calling callback function'
  File "/Users/calin/Library/Developer/CoreSimulator/Devices/80F9328C-18C7-43C1-9E93-F4CB128AD3CA/data/Containers/Bundle/Application/009B9605-E170-441C-AE5A-DF4CFBC80BDE/Electron-Cash.app/Library/Application Support/com.c3-soft.ElectronCash/app_packages/rubicon/objc/runtime.py", line 1040, in _objc_method
    result = f(py_self, *args)
  File "/Users/calin/Library/Developer/CoreSimulator/Devices/80F9328C-18C7-43C1-9E93-F4CB128AD3CA/data/Containers/Bundle/Application/009B9605-E170-441C-AE5A-DF4CFBC80BDE/Electron-Cash.app/Library/Application Support/com.c3-soft.ElectronCash/app/ElectronCash/electroncash_gui/ios_native/send.py", line 25, in init
    self = ObjCInstance(send_super(self, 'init'))
  File "/Users/calin/Library/Developer/CoreSimulator/Devices/80F9328C-18C7-43C1-9E93-F4CB128AD3CA/data/Containers/Bundle/Application/009B9605-E170-441C-AE5A-DF4CFBC80BDE/Electron-Cash.app/Library/Application Support/com.c3-soft.ElectronCash/app_packages/rubicon/objc/runtime.py", line 684, in send_super
    superclass = get_superclass_of_object(receiver)
  File "/Users/calin/Library/Developer/CoreSimulator/Devices/80F9328C-18C7-43C1-9E93-F4CB128AD3CA/data/Containers/Bundle/Application/009B9605-E170-441C-AE5A-DF4CFBC80BDE/Electron-Cash.app/Library/Application Support/com.c3-soft.ElectronCash/app_packages/rubicon/objc/runtime.py", line 565, in get_superclass_of_object
    cls = libobjc.object_getClass(obj)
ctypes.ArgumentError: argument 1: <class 'RecursionError'>: maximum recursion depth exceeded while calling a Python object
(lldb) 

I'm on iOS, using latest rubicon from github.

I'm presuimg the send_super call on MyB leads to some infinite recursion?

This works fine though:

a = MyA.new().autorelease()
print("a prop = %s"%(str(a.aProp)))

Any thoughts are appreciated. Also, if this is fixable -- I volunteer to fix it if you point me in the right direction in the code and/or know some of the gotchas.

Thanks for any ant all thoughts on this!

Release v1.0

At some point, a v1.0 release is inevitable. What features do we need to add before we formalise APIs and push the big One Oh?

Things with the name objc

Currently there are three things in rubicon.objc that have the name objc:

  • The rubicon.objc package
  • The objc module inside the rubicon.objc package
  • The objc CDLL library inside the objc module

This would normally be okay, as most people probably only use the rubicon.objc package. However rubicon.objc's __init__.py imports the objc CDLL from the objc module. This hides the rubicon.objc.objc module, which cannot be accessed at all from outside. (The name rubicon.objc.objc corresponds to the CDLL, not the module.) Not all names from the rubicon.objc.objc module are imported into the rubicon.objc package, so it is sometimes necessary to access the module (for example when debugging).

To resolve this, either the module or the CDLL could be renamed. Renaming the module would be fully backwards-compatible, as the module was never accessible by its name before. The CDLL is accessible from outside, so renaming it would break any code that uses it.

Property falsely returns Method insted of int

The Problem happens in the header of the for loop at filenames.count (in the following code). When looking into the docs I see that count is a property but it's returning a method at the moment. filenames is a <__NSSingleObjectArrayI 0x100868e30>. I think this problem is related to #30 but I'm not sure how to fix it.

 @objc_method
    def application_openFiles_(self, app, filenames) -> None:
        //print("open file ", filenames)
        for i in range(0, filenames.count):
            filename = filenames.objectAtIndex(i)
            if isinstance(filename, str):
                fileURL = NSURL.fileURLWithPath(filename)

this is the error log:

Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 234, in 'calling callback function'
  File "/Users/Jonas/Documents/Programming/PyBee/rubicon-objc/rubicon/objc/objc.py", line 1069, in _objc_method
    result = f(py_self, *args)
  File "/Users/Jonas/Documents/Programming/PyBee/toga/src/cocoa/toga_cocoa/app.py", line 53, in application_openFiles_
    for i in range(0, filenames.count):
TypeError: 'method' object cannot be interpreted as an integer

Can't define callback with method that returns structure

ctypes allows you to define a method that returns structures. However, a callback method can't return a structure - only primitive types.

This is reported as Python issue5710.

However, there are some methods in the Cocoa and iOS APIs that require returning NSSize or NSRect. NSWindow windowWillResize:toSize: is one notable example.

So - we either need to fix the Python issue to allow structures to be returned by callbacks, or we need a workaround. One thought for a workaround - it may be possible to do this by casting a structure to a void pointer to get it through ctypes, then reversing the cast on the other side.

The return_struct branch contains a test for this problem; the test fails when the callback method on the handler class is registered.

NSAttributedString Creation - No Errors with Wrong Number of Arguments

Current Behavior: no error when creating an NSAttributedString with the wrong number of arguments.

Input Code

from ctypes import *
from ctypes import util
from rubicon.objc import *


appkit = cdll.LoadLibrary(util.find_library('AppKit'))
NSFont = ObjCClass('NSFont')
NSFontAttributeName = objc_const(appkit, "NSFontAttributeName")

NSAttributedString = ObjCClass('NSAttributedString')
textAttributes = NSMutableDictionary.alloc().init()
textAttributes[NSFontAttributeName] = NSFont.systemFontOfSize(13)
text = "Tiberius"
text_string = NSAttributedString.alloc().initWithString_(text, textAttributes)

Expected behavior: ArgumentError when trying to call the ObjCInstance. For the last line I should have called:

text_string = NSAttributedString.alloc().initWithString_attributes_(text, textAttributes)

Environment

Rubicon-objc master latest
MacOS 10 High Sierra 10.13.5

Possible Solution

Possible issue with converting arguments in the ObjCMethod class, should get_callable return an ArgumentError?

Suggestion / recipe for exporting decorated functions

For example:

def logged(f):
    @functools.wraps(f)
    def inner(*args, **kwargs):
        return f(*args, **kwargs)
    return inner


class CantRoller(UIViewController):
    @objc_method
    @logged
    def collectionView_cellForItemAtIndexPath_(self, view, path):
        ...

This fails at runtime with:

Traceback (most recent call last):
  File "ui/__main__.py", line 102, in inner
    return f(*args, **kwargs)
TypeError: collectionView_cellForItemAtIndexPath_() missing 2 required positional arguments: 'view' and 'path'
Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 234, in 'calling callback function'
  File ".../site-packages/rubicon/objc/objc.py", line 1003, in _objc_method
    result = f(py_self, *args)
  File ".../site-packages/ui/__main__.py", line 102, in inner
    return f(*args, **kwargs)
TypeError: collectionView_cellForItemAtIndexPath_() missing 2 required positional arguments: 'view' and 'path'

I'm guessing rubicon looked up inner's function signature at objc_method decoration time and now at call time doesn't pass the arguments?

Actually I'm confused what's wrong...

Improve memory management documentation

Looking at old SO discussion (e.g. https://stackoverflow.com/a/6610/705086 ) it looks like when SomeOBJCClass.alloc().init() is called from Python code, it becomes Python's responsibility either to .release() the resulting object on the spot, or to mark it for GC at the bottom of event loop using .autorelease().

Is that correct?

If that's correct, it must be documented...

It is possible to create blocks receive ObjCInstance argument?

For instance, if I would like to create a block to receive UIGestureRecognizer instance, I create a method like this.

def handle_gesture(gesture: ObjCInstance) -> None:
print(gesture)

view.addCustomGesture(UITapGestureController.alloc().initWithHandler(handle_gesture))
it looks like it does create the gesture correctly, but the callback didn't being called.

I know I can use the vanilla selector in together with addGesture method, but I would like to know if it is possible to create block callbacks receive a ObjCInstance and handle in python side. I've taken a look at the test folder, it did include a few tests of type of (int,int), but didn't find any information about how does block running process went like here(including the gc part of python instance relate to block etc.)

Any help would be real appreciated :)

Should ObjCClass stay a subclass of type?

At the moment ObjCClass extends ObjCInstance and type. As far as I can tell, the fact that it extends type is not used or needed for anything at the moment. A metaclass don't need to subclass type, it's enough to have its constructor accept the signature of type's constructor (name, bases, namespace).

It's also misleading, because users might expect to be able to define Python methods on Objective-C classes (by not decorating them with @objc_method). This is not possible at the moment, because instances of user-defined Objective-C classes are actually instances of ObjCInstance, and not of the user's ObjCClass subclass. We could theoretically support this though - similar to how an ObjC NSArray becomes a Python ObjCListInstance, we could make an ObjC UserSubclass become a Python UserSubclass too. (The logic in ObjCInstance._select_mixin would probably need to be optimized though, I don't think a chain of isKindOfClass: scales well.)

iOS error - ValueError: Library 'c' not found

(crossposting from beeware/briefcase#48)

I'm trying to use briefcase to make an iOS app, but am finding that when I follow the second tutorial and try and run the app on a physical device, it crashes with the following error:

2017-05-26 14:00:53.583 Tutorial 0[11917:5779110] PYTHONPATH is: PYTHONPATH=/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app:/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app_packages
2017-05-26 14:00:53.584 Tutorial 0[11917:5779110] Initializing Python runtime
2017-05-26 14:00:54.830 Tutorial 0[11917:5779110] Running /var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app/tutorial_0/__main__.py
Traceback (most recent call last):
  File "/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app/tutorial_0/__main__.py", line 1, in <module>
    from tutorial_0.app import main
  File "/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app/tutorial_0/app.py", line 1, in <module>
    import toga
  File "/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app_packages/toga/__init__.py", line 114, in <module>
    set_platform()
  File "/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app_packages/toga/__init__.py", line 86, in set_platform
    local_vars['platform'] = importlib.import_module(module_name)
  File "/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Python.framework/Resources/lib/python35.zip/importlib/__init__.py", line 126, in import_module
  File "/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app_packages/toga_iOS/__init__.py", line 2, in <module>
    from .app import *
  File "/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app_packages/toga_iOS/app.py", line 1, in <module>
    from rubicon.objc import objc_method
  File "/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app_packages/rubicon/objc/__init__.py", line 3, in <module>
    from .objc import (
  File "/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app_packages/rubicon/objc/objc.py", line 27, in <module>
    c = cdll.LoadLibrary(_find_or_error('c'))
  File "/var/containers/Bundle/Application/C6D15E50-4F2F-41B3-9CEE-CF09CF1D4637/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app_packages/rubicon/objc/objc.py", line 23, in _find_or_error
    raise ValueError("Library {!r} not found".format(name))
ValueError: Library 'c' not found
2017-05-26 14:00:58.937 Tutorial 0[11917:5779110] Application quit abnormally!
2017-05-26 14:00:59.029 Tutorial 0[11917:5779110] Leaving

If I run the same app on the Simulator instaed, everything works fine:

2017-05-26 14:30:10.614 Tutorial 0[2607:70813] PYTHONPATH is: PYTHONPATH=/Users/David/Library/Developer/CoreSimulator/Devices/53742BCD-50FC-4DA8-B85D-6938B1888F3B/data/Containers/Bundle/Application/A85A882F-8322-4C1D-B62F-D919722D4999/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app:/Users/David/Library/Developer/CoreSimulator/Devices/53742BCD-50FC-4DA8-B85D-6938B1888F3B/data/Containers/Bundle/Application/A85A882F-8322-4C1D-B62F-D919722D4999/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app_packages
2017-05-26 14:30:10.615 Tutorial 0[2607:70813] Initializing Python runtime
2017-05-26 14:30:11.032 Tutorial 0[2607:70813] Running /Users/David/Library/Developer/CoreSimulator/Devices/53742BCD-50FC-4DA8-B85D-6938B1888F3B/data/Containers/Bundle/Application/A85A882F-8322-4C1D-B62F-D919722D4999/Tutorial 0.app/Library/Application Support/org.pibakery.tutorial_0/app/tutorial_0/__main__.py
App finished launching.

The simulator is running iOS 9.3, the iPad is running iOS 9.3., and I'm using Xcode 7.3.1 on Mac 10.11.6.

trunk backwards incompatible

Following works with 0.2.3 (pypi), but does not work with trunk (github)

UIColor = ObjCClass("UIColor")
UIColor.blueColor()
TypeError: 'ObjCInstance' object is not callable

Perhaps properties don't need to be called now?

Perhaps it just deserved a release note?

Remove automatic NSString-to-str conversion

As discussed on Gitter, this behavior is problematic when you want to create a new NSString using a factory or init method, or when you want to call methods on a NSString returned from a method.

Instead, an ObjCStringInstance class should be added, which adds a Pythonic interface to NSString objects.

Instance properties defined by protocol on superclass are not autodiscovered

On iOS, UITextField defines a keyboardType property. However, this property doesn't appear to get picked up by Rubicon.

Other properties (like borderStyle) work fine.

If you manually invoke send_message(obj, 'setKeyboardType:', ...), it works, too.

So - there's evidently something about the property discovery process.

A little digging revealed that the keyboardType() and setKeyboardType() accessor and mutator aren't found by cache_property_methods(). This may be due to the fact that they're defined on the UITextInputTraits protocol, rather than on a direct superclass of UITextField.

Objective-C exception handling support

This one is a bit tricky. On all modern architectures, Objective-C exceptions are implemented using libunwind, the same system as C++ exceptions. Unfortunately it's not possible to handle these exceptions using only ctypes. We would need a small C helper function, something like bool callAndCatchException(void *(*func)(void *), void **outRetval, id *outException) that calls the function pointer, catches any exception that comes out, and reports success/failure as well as the return value or exception.

The other question is how we should expose Objective-C exceptions on the Python side. Should we route every Objective-C method call through our helper function, and automatically wrap any Objective-C exceptions in Python exceptions? Or should we provide an extra function to allow the user to do this on demand? I would be in favor of the latter option, because using callAndCatchException adds extra overhead even when no exceptions are caught (not just the call, but also the creation of the wrapper func). There's also not that much benefit in converting Objective-C exceptions to Python exceptions if they won't be handled anyway - in fact it's probably more useful not to convert them, to get a proper crash report with the native stack trace and debugging info.

Crash under GCC's libobjc due to lack of class_setSuperclass

When trying to run Podium on Linux for further development I faced this issue when it tried to use this library:

Traceback (most recent call last):
  File "/home/bruno/anaconda3/bin/podium", line 11, in <module>
    load_entry_point('podium', 'console_scripts', 'podium')()
  File "/home/bruno/anaconda3/lib/python3.4/site-packages/pkg_resources/__init__.py", line 565, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/home/bruno/anaconda3/lib/python3.4/site-packages/pkg_resources/__init__.py", line 2589, in load_entry_point
    return ep.load()
  File "/home/bruno/anaconda3/lib/python3.4/site-packages/pkg_resources/__init__.py", line 2249, in load
    return self.resolve()
  File "/home/bruno/anaconda3/lib/python3.4/site-packages/pkg_resources/__init__.py", line 2255, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "/home/bruno/PycharmProjects/podium/podium/__main__.py", line 1, in <module>
    from .app import Podium
  File "/home/bruno/PycharmProjects/podium/podium/app.py", line 5, in <module>
    from podium.deck import *
  File "/home/bruno/PycharmProjects/podium/podium/deck.py", line 3, in <module>
    from rubicon.objc import ObjCClass, objc_classmethod, objc_method
  File "/home/bruno/anaconda3/lib/python3.4/site-packages/rubicon/objc/__init__.py", line 3, in <module>
    from .objc import (
  File "/home/bruno/anaconda3/lib/python3.4/site-packages/rubicon/objc/objc.py", line 144, in <module>
    objc.class_setSuperclass.restype = c_void_p
  File "/home/bruno/anaconda3/lib/python3.4/ctypes/__init__.py", line 364, in __getattr__
    func = self.__getitem__(name)
  File "/home/bruno/anaconda3/lib/python3.4/ctypes/__init__.py", line 369, in __getitem__
    func = self._FuncPtr((name_or_ordinal, self))
AttributeError: /usr/lib/x86_64-linux-gnu/libobjc.so.4: undefined symbol: class_setSuperclass

After some research I found out that not only my version of libobjc (4.9.2-10, from Debian jessie) lacks the aforementioned call but it's not present on GCC's trunk yet to this day.
Perhaps replacing it with GNUstep's libobjc2 solves it, but it needs building from source on Debian-based platforms, which certainly does not help with distribution.

How to run rubicon-objc on linux

I got the following error

AttributeError: python: undefined symbol: PyType_stgdict

when I tried to import rubicon.objc on Ubuntu. Is there a way to use this library on Linux? If not currently, what do I need to do to make it work?

Pythonic interface for NSArray / NSMutableArray

In the context of #14, a light discussion on a Pythonic interface for NSArray came up. The idea is to provide a sequence-like interface (collections.abc.Sequence?) that would simplify Python side NSArray manipulation such that, for example, instead of iterating like:

things = obcj.methodReturningNSArray()
for i in range(things.count()):
    thing = things.objectAtIndex_(i)
    do_something_with(thing)

...one could:

things = objc.methodReturningNSArray()
for thing in things:
    do_something_with(thing)

Other desirable/possible interfaces:

  • Indexing as in things[index]
  • Length as in len(things)
  • Write-ability things[index] = something
  • Maybe a factory-like class method that would build an NSArray from a Python sequence.

Final notes:

  • Implementation strategy could be either via wrapper or by customizing the Python NSArray/NSMutableArray class that rubicon-creates.
  • Something similar would be desirable for NSDictionary / NSMutableDictionary.

Thanks.

0.2.6 issues

Running Sierra, xCode 8.3

Running the Briefcase Tutorial 2, using toga 0.2.8, and rubicon-objc 0.2.6

$ python -m appname
Traceback (most recent call last):
  File "/Users/kmclaughlin/.pyenv/versions/3.5.1/lib/python3.5/runpy.py", line 170, in _run_module_as_main
    "__main__", mod_spec)
  File "/Users/kmclaughlin/.pyenv/versions/3.5.1/lib/python3.5/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/Users/kmclaughlin/git/pybee/tutorial1/appname/appname/__main__.py", line 4, in <module>
    main().main_loop()
  File "/Users/kmclaughlin/git/pybee/tutorial1/venv/lib/python3.5/site-packages/toga_cocoa/app.py", line 137, in main_loop
    self._startup()
  File "/Users/kmclaughlin/git/pybee/tutorial1/venv/lib/python3.5/site-packages/toga_cocoa/app.py", line 89, in _startup
    self.resource_path = os.path.dirname(os.path.dirname(NSBundle.mainBundle().bundlePath))
TypeError: 'ObjCInstance' object is not callable

Substituting out rubicon for the version that was up yesterday, 0.2.4 (0.2.5 may have been skipped?)

$ pip install rubicon-objc==0.2.4
$ python -m appname

Gives me a valid f2c

Bi-direction object access

I was working through your Quickstart examples. Is there a way to access an object in objective c that was created in python using the Rubicon bridge? In your example you created the my_handler object, can the methods of this object be accessed from objective c? I am trying to figure out a way to shuttle results back from an embedded python interpreter to an objective c program. Your examples are all on the python side. Thanks

Reducing the number of Travis test configurations

At the moment we have six Travis configurations that run the tests on all Xcode versions from 6.4 to 8.0, and on various OS X/macOS versions. This unfortunately means that for each Travis build, we need to wait for six free build slots, and that can take a while. On https://www.traviscistatus.com/#week you can see that a backlog for the OS X builds is quite common, often around evening/night UTC.

My suggestion would be to update the Xcode version list to the following:

  • xcode6.4 (OS X 10.10)
  • xcode7.3 (OS X 10.11)
  • xcode8 (OS X 10.11)
  • xcode8.2 (macOS 10.12)

This way we test on every major Xcode version available on each OS X/macOS version, and we only need four instead of six slots.

The OS X versions are taken from the Travis docs. The comment in the .travis.yml says that xcode8 uses macOS 10.12, but that seems to be incorrect - xcode8 uses Darwin 15.6.0 (example), which is what OS X 10.11 is based on, so the Travis docs are correct here.

Minor (?) issue with PyInstaller

Hello, thanks for sharing the project. I particularly like the light approach at interfacing with ObjC libraries when compared with PyObjC.

I was playing around with it and successfully interfaced with the Address Book in my Mac: I created a simple script to dump its entries to stdout.

Then I went further I tried to package it with PyInstaller -- unfortunatelly, the frozen version failed to run.

I found the culprit to be the following line, in objc.py:

objc = cdll.LoadLibrary(util.find_library(b'objc'))                                       

On my environment, running Mac OS X 10.9.5, Python 3.5.2, I found out that ctypes.util.find_library returns None when given bytes but works when given text:

Python 3.5.2 (default, Jul 16 2016, 11:57:33) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import util
>>> util.find_library(b'objc')
>>> repr(util.find_library(b'objc'))
'None'
>>> util.find_library('objc')
'/usr/lib/libobjc.dylib'

After changing the aforementioned line in objc.py to use text instead of bytes -- that is 'objc' instead of b'objc' -- the PyInstaller frozen version ran successfully.

Of course, the mystery is explaining how on earth the non-frozen script runs -- I double checked that both were running under the same Python 3.5.2 virtual env.

I took a look at the ctypes documentation and I couldn't find clear information on whether the name argument to ctypes.util.find_library should be binary or text.

So, the questions are:

  • Why did you use bytes instead of text for find_library?
  • Does the code above behave differently in your environment?

Thanks again.
Regards.

Wrong automatic conversion of NSDecimalNumber to python double

The to_number function of core_foundation.py automatically converts a CFNumber to the proper python numeric type, but since CFNumberGetType() returns kCFNumberDoubleType for instances of NSDecimalNumber, the method has the side effect of converting a NSDecimalNumber to a double, which is incorrect.

I would suggest to patch the to_value and from_value methods in core_foundation.py to convert NSDecimalNumber to Decimal python object and viceversa.
I managed to patch the to_value, but my naive approach to patch the from_value method is not working, since the result of decimalNumberWithString_ is again converted to python Decimal by the to_value method itself.

Please help.

def to_value(cftype):
    """Convert a CFType into an equivalent python type.
    The convertible CFTypes are taken from the known_cftypes
    dictionary, which may be added to if another library implements
    its own conversion methods."""
    if not cftype:
        return None
    if cftype.isKindOfClass_(ObjCClass('NSDecimalNumber')):
        return Decimal(ObjCClass('NSString').stringWithFormat_("%@", cftype))
    typeID = cf.CFGetTypeID(cftype)
    if typeID in known_cftypes:
        convert_function = known_cftypes[typeID]
        return convert_function(cftype)
    else:
        return cftype
def from_value(value):
    """Convert a Python type into an equivalent CFType type.
    """
    if isinstance(value, Decimal):
        # ??? HOW TO DO THIS ???
        # return ObjCInstance('NSDecimalNumber').decimalNumberWithString_(str(value))
    if isinstance(value, text):
        return at(value)
    elif isinstance(value, bytes):
        return at(value.decode('utf-8'))
    else:
        return value

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.