Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PyObjC and custom blocks

The official documentation says that it is possible to use custom blocks in python code but you need to create metadata. I haven't found an example of it.

My question is how to create, use and distribute metadata for custom blocks.

Example

@interface SomeClass

- (void)doSomethingWithCompletion: (void (^)(SomeObject *obj, NSError *error))myBlock;

@end


def pythonMethod():
    def completion(obj, error):
        # staff
    foo = SomeClass.new()
    foo.doSomethingWithCompletion_(somehow_pass_completion)

The question is how somehow_pass_completion should look like and how to provide metadata for myBlock.

like image 334
Kentzo Avatar asked May 04 '11 18:05

Kentzo


1 Answers

The metadata that is mentioned is information, stored in XML format, about the return and argument types of Objective-C methods. It is needed so that the PyObjC bridge knows what type to turn a Python object into when passing it back across to Objective-C code. You can check out the metadata if you like; it's in .bridgesupport files inside of the PyObjC framework. The AppKit metadata, for example, is at /System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python/PyObjC/AppKit/PyObjC.bridgesupport It can be generated for any Objective-C code you have using Apple's gen_bridge_metadata command-line tool. Theres a man page for that utility, and man 5 BridgeSupport is also informative.1

PyObjC provides the functions objc.registerMetaDataForSelector and objc.parseBridgeSupport, both of which allow you to add metadata for your methods, using either Python dicts (the former function) or the XML format described in the BridgeSupport man page (the latter). Examples of using registerMetaData... are available in the pyobjc source at: pyobjc/pyobjc-core/PyObjCTest/test_metadata* (and nearby test_metadata*.py files)2.

Just as an example, this is the metadata for -[NSSavePanel beginWithCompletionHandler:], which takes a block as an argument:

<method selector='beginWithCompletionHandler:'>
    <arg index='0' block='true' >
        <retval type='v' />
        <arg type='i' type64='q' />
    </arg>
</method>

The arg type specifiers are the same Type Encodings that you get back from using @encode in Obj-C. The metadata for your method should be quite similar.

So, since you have your methods prototyped in Objective-C, you should be able to run them through gen_bridge_metadata to create a .bridgesupport file that you can include in your project, then use objc.parseBridgeSupport to read that file in. Using objc.registerMetaDataForSelector has also worked for me in the past; check out the linked examples above.

Once you have the metadata in the PyObjC "system", you can use any old callable object as an argument for the method that takes a block:

def pythonMethod():
    def myCompletionHandler(obj, error):
        pass
foo = SomeClass.new()
foo.doSomethingWithCompletion_(myCompletionHandler)

This is all kind of an amalgam of a few other posts I've made here about PyObjC: PyObjC and Returning Out Parameters | Indexed Accessor Method | Problem with openPanelDidEnd. You might want to take a look at those too.

It's been a while since I've futzed with this, so I may have left something out. Don't hesitate to ask for more info if you still can't get this to work. Don't give up too easily, though -- the bridge is a funny beast and sometimes you just have to show it who's boss!


1Another Apple doc you should read is: Generating Framework Metadata.
2I discovered these via a pyobjc-dev mailing list thread

like image 102
jscs Avatar answered Nov 01 '22 18:11

jscs