I’ve been trying to learn PyObjC by going through Aaron Hillegass’s excellent Cocoa Programming for Mac OS X, doing the exercises in Python instead of Objective C. For the most part the translation between Python and Objective C provided by PyObjC is remarkably seamless, but of course I immediately ran into one of the seams.
I was trying to bind an NSTableView to an array stored in a model object, by way of an NSArrayController. The array was named “employees”, and so I needed to implement collection accessors for inserting and removing items, following the patterns
<Key> is the capitalized name of the collection. So in Objective C these would look like:
- (void) insertObject:(id) obj inEmployeesAtIndex: (unsigned int) index;
- (void) removeObjectFromEmployeesAtIndex: (unsigned int) index;
The translation to Python is simple, just replace colons with underscores and add the self parameter:
def insertObject_inEmployeesAtIndex_(self, obj, index):
def removeObjectFromEmployeesAtIndex_(self, index):
I ran the app, and got an error as soon as I tried to remove an item from my table, causing the
removeObjectFromEmployeesAtIndex_ method to be called. It seemed that the index parameter was being set to None (Python’s nil). How was that happening?
The problem was the type of the index parameter — Cocoa was passing an unsigned int, which happened to be zero. But PyObjC defaults to treating all parameters as objects, so that zero was treated as the nil object. PyObjC knows about the methods of lots of Cocoa classes, and can construct special cases so they all work automatically, but it couldn’t know about this call — it couldn’t know that I’d name my array employees, and therefore need those specific collection accessors. I had to find a way to tell PyObjC that my functions take an unsigned int as a parameter.
Unfortunately, the documentation for how to do this is scattered around. Examples are mentioned in passing in the introduction and How to wrap an Objective-C class library documents, and I got the idea that the
signature functions in the
objc module were involved. So I fired up the Python interpreter and typed
print objc.selector.__doc__ and
print objc.signature.__doc__. But the most helpful reference was Geoff Wilson’s Showing a NSSavePanel as a sheet, a blog post that described solving a similar problem with an NSSavePanel callback.
The solution, was to follow the definition of
insertObject_inEmployeesAtIndex_ = objc.selector(
signature = 'v@:@I'
and to follow the definition of
removeObjectFromEmployeesAtIndex_ = objc.selector(
signature = 'v@:I'
In Python 2.4 it’s a bit simpler; you can preceed the definition of
and the definition of
objc.signature tell PyObjC what sort of arguments to expect from Objective C. Figuring out the signature is the one tricky part, but fortunately these functions are simple. Apple documents the supported type encodings. For example,
'v@:I' indicates a function that returns void (
'v'), is an object method (
'@:'), and takes one unsigned integer argument (
'I'). Things get more complicated with pointers, output parameters, etc. I do think it would be helpful if the PyObjC documentation included an extensive collection of example method signatures, along with a more explicit explanation for when objc.selector and objc.signature are required.