Skip navigation

I’ve learned a lot since my last post about writing Python apps for the (jailbroken) iPhone. My current focus is in building a viable UI to house the app that I want to write. This has so far involved heaping servings of UIKit with a dash of CoreGraphics to write a custom dialer control

Through the course of this, I’ve noticed a lot of caveats and gotchas that are not well described in the existing iPhone Python information available on the web. Hopefully this info will be useful for the next iPhone Python dev who is desperately googling for the information they need!

Firstly, Apple is very bad at reorganizing data without thoroughly breaking URLs. The Objective C documentation specifies a compact encoding for representing method signatures, which is used by PyObjC as a guide when marshalling data between Python and the C world. Normally the user of an ObjC class need not worry about these encoded signatures, as Objective-C and Apple’s frameworks provide enough metadata to figure them out automagically. However, some frameworks (such as CoreGraphics and UIKit) export functions which are not part of any Objective-C class, but are instead normal C accessible functions. In the case of CoreGraphics, I presume this is to allow C applications to easily leverage the same API as Objective-C ones.

PyObjC is unable to infer the signatures of these methods, because they are not covered by any Objective-C metadata. This makes sense, since they aren’t Objective-C signals anyway. So in these situations, the functions need to be imported from the Python side using a special method of the ‘objc’ object like so:

objc.loadBundleFunctions(BundleObject, globals(), [("Method1", "Signature1"), ...])

The format for the ‘Signature1’ part is documented by Apple. Several blogs about this subject linked to the information, but Apple reorganized the whole document and left a broad redirect from the old locations to the index of the new version. It took me a very long time to find the information in the new structure, because there was no obvious name for the feature! For the record, they are “encoded signatures” and the process is “signature encoding”. I’m putting the information here to avoid relying on Apple for it.

Signature Encoding Format

A signature consists of a set of characters, which each represent an element of the method’s signature. The elements include the return type, implicit arguments (like the instance pointer and selector for Objective-C messages), and explicit arguments. A number of modifiers are supported as well.

@   - object
i,I - int, unsigned int
:   - method selector (SEL)
@:  - instance and selector, means function is an
instance method (use after return type, before args)
V,v - void
B   - C++ bool (0 = false, other = true)
f   - float
d   - double
*   - charptr
s,S - short, unsigned short
l,L - long, unsigned long
q,Q - long long, unsigned long long
bn  - bit field of n bits
?   - unknown type, void*, function pointers
c   - signed char
C   - unsigned char
^t  - pointer to type t
#   - objc Class
[nt]- array of length n containing type t
{name=t..} - structure, where 't..' is the order and type of fields
(name=t..) - union, where 't..' is the types of union contents

Marshalling Strings
PyObjC accepts normal python strings for char* elements (‘*’). NSString is exposed to Python as a subclass of the standard unicode type, which allows them to be immediately usable. You can also provide any Python string in place of an actual NSString. Getting an *actual* NSString object is hairy however, so it seems the best calling method to use for NSString methods is NSString.methodName_(stringObject, arg1, …)

In Python, you instantiate Objective-C objects the same way you would in Objective-C. You use alloc() and an init() method or a combined create() method depending on the provided API. This doesn’t change when inheriting Objective-C classes in Python. You need only create an init() method for your subclass which calls the appropriate init() of the superclass, and don’t forget to return self at the end! PyObjC takes care of creating an appropriate alloc() which returns an object of your new type.

So an example constructor might look like:

def MyClass:
def initWithFrame_(self, frame):
self = super(MyClass, self).initWithFrame_(frame)
return self

Note that the call to the superclass method initWithFrame_ will differ depending on what class you are inheriting from.

CoreGraphics is an ugly API. To work well in all languages, the API is function-oriented– which is all well and good until you add Apple’s ridiculously bloated naming conventions (“CGContextGetUserSpaceToDeviceSpaceTransform” anyone?) Since PyObjC can’t discover normal
functions on it’s own, you have to import them individually using objc.loadBundleFunctions (see above). PyObjC provides a wrapper module for Quartz but unfortunately it is not provided in the pyobjc distribution I have installed on my iPod, and it uses Objective-C helper methods which need to be compiled (I still do not have a working toolchain), so I cannot yet use it.

To make CoreGraphics integrate better with Python I am building an object-oriented wrapper API which resembles cairomm. It strips operation names of unnecessary words and makes the details easier to remember. PyObjC by default marshals CGRect, CGPoint, CGSize etc into tuples. CGRect is actually a tuple of 2 tuples which each have 2 floats (ie, ((x, y), (w, h))). I’ve made Python types which can take a tuple representation and make it more accessible with named fields:

rect = CGRect(window.frame())
print "the x coord: " + str(rect.x)
print "the whole thing: " + rect.toString()

Enums are also not imported automatically by PyObjC, so to retain the sanity of the code, I am building subclasses of int to pin the proper values to their names.

I plan to clean up the code and release it as a reusable iPhone Python toolkit, but that’s for the next part of this ongoing series. I’ll also talk about performance and Springboard integration, among probably many other things.

Happy Hacking!


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: