Skip navigation

Monthly Archives: November 2009

This is the third part of my Python iPhone development series. Here are the first and second parts.

Some performance testing
I’ve done some preliminary performance testing with results you can seethe at. It takes approximately 7 seconds to start iPhone Python apps. This is excruciatingly slow, especially for some of the apps I’m writing which replace builtin apps that load damn-near instantly. I’ve observed that almost 100% of this time is spent loading PyObjC and performing bundle/function loading (objc.loadBundle/obj.loadBundleFunctions)

There’s virtually nothing that can be done about this except to somehow make PyObjC faster. On the bright side, defining a large amount of classes (like enumerations) adds almost no startup time, which is good for the toolkit I’m writing (see below).

Performance within the apps is pretty decent, but here it is obvious that there is some overhead in the translation between Objective C and Python. I created a simple custom dialer control using CoreGraphics, implemented with a single image displayed nine times (one for each digit) and 9 seperate renderings of a single character each. When you tap each button, the whole view is repainted with the selected button highlighted, and then repainted again when the finger is lifted. There is a noticeable lag when redrawing. This is a naive way to implement a control (drawRect gives you a clipping rectangle for a reason, use it!), but the performance of such simple operations should presumably be better. I’m well aware that CoreGraphics is not the fastest public graphics API available and have not drafted a similar test in Objective C for lack of a toolchain to do so, so please let me know if this operation in CoreGraphics just sucks.

Memory usage is actually quite reasonable, which is very good considering the paltry 50-60M of RAM apps have to work with when you subtract the amount used by the iPhoneOS and Springboard. A simple app I wrote to display debug logs weighs in at an RSIZE of about 10M (VSIZE 67M). Comparatively, MobileSafari in it’s native backgrounded state with two tabs open weighs 16M RSIZE, 95M VSIZE. MobileAddressBook (a small, simple application similiar in scope to my debug logs app) weighs 12M RSIZE, 68M VSIZE when in the foreground. These are not scientifically gathered numbers, but it’s not unreasonable to assume this is pretty typical. Even with the additional weight of the Python runtime, memory usage is not significantly increased.

Springboard App Launching Restrictions

Perhaps these are documented elsewhere but it’s worthwhile to get another copy out onto the web. Springboard exercises a number of restrictions on the apps that it starts. These restrictions do not apply to normal processes in so much as that matters considering you cannot run UIKit apps from the command line anyway.

  • The process which calls UIApplicationMain must lie within the application’s bundle. This is why the example Python app bundles contain a symbolic link to /usr/bin/python. This means you cannot directly start a Python app using the interpreter directive (“#!/usr/bin/python”)
  • The process ID must be the same as the one spawned by Springboard. This is why the BASH script does ‘exec’ and it must be this way. You cannot spawn a new process to take care of the app’s UI, though it seems to be perfectly acceptable to start processes to do other things.
  • When Springboard launches an app which uses a script as it’s executable, it checks that the interpreter directive points to an executable within ‘/bin’ (there may be other allowed applications but ‘/bin’ is the only one I’ve confirmed). Scripts using /usr/bin interpreters (or any other directory) will not be executed.

So why does this matter? Well currently Python apps on the iPhone are a bit clunky. You’ve got a BASH script which execs your symbolic link to Python with your app’s main python script as an argument, and optionally does some logging redirection. I discovered most of these limitations by trying to take BASH out of the equation. I failed at this for these reasons, though it seems entirely possible to create a simple C app which does the setup much quicker than loading BASH. For now though I’ve found a clever solution, which at least cleans up an app’s directory structure. Toss this header onto your app’s main Python script, make it executable, and set it in your Info.plist’s CFBundleExecutable key:


#!/bin/bash
"""":
# This section is a BASH initialization script which sets up logging of stderr
# to /var/mobile/.err.log to catch Python errors
exec "$(dirname "$0")"/Python -u $0 2>/var/mobile/`basename $0 .py`.err.log
# -----------------------------------------------------------------------#
# End BASH, Begin Python """
import sys
...

As you might be able to see, the script is first executed by BASH. The line “”””: is both a no-op in BASH and the beginning of a
multi-line comment in Python (the last double quote and colon are ignored as part of the comment).
Since we do an ‘exec’ within BASH on ourself, nothing past the long dash line is executed. When Python takes over, it starts interpreting from the ‘import sys’ line. This approach is clean and simple, allows you to free up slots in your editor, and makes editing your logging directives easier. As in my previous BASH app launcher examples, this will create a log in /var/mobile with the name MyCoolApp.err.log, where MyCoolApp is the name of your app (at least, according to the bundle’s directory name).

An iPhone Toolkit for Python

As I briefly mentioned in the last installment, I am working on a Python toolkit for iPhone app development. The toolkit will do all the PyObjC wrapping for you, and will provide a large number of convenience classes which ease the process of writing your apps. The toolkit will also include a set of scripts which make testing and debugging your code much easier.

A surprising number of iPhone APIs are C-oriented, probably owing to the limited storage and memory of the devices which employ iPhoneOS. These APIs will be offered in their awkward C glory in addition to Python-specific APIs which are much easier to use. The toolkit already contains a Python class for CoreGraphics contexts, and (more or less) most of the API is already covered. The toolkit will strive to create bindings for private frameworks as well (to the extent possible, given the near complete lack of community documentation on them). Why would we not provide them considering Apple has already banned your Python apps from being listed in the app store?

Having log files of your app’s standard output and python errors is great, but having to type a command to print the log or in some way refresh your view of it is tedious. It would be better if we had a view of the output log as it happened.

Initially I tried to create a cradle of sorts for launching Python apps in a “debugging mode”. This mode would detect exceptions and Python crashes and pop into the debug log viewer application I wrote. This MIGHT still be possible, but has so far thwarted my discovery due to the Springboard restrictions listed above. If anyone has more information on how to facilitate this I would love to know.

It is possible however to have a live view of Python’s output. Like any decent UNIX implementation, iPhoneOS supports named pipes, also known as FIFOs. Named pipes are special files which allow two processes to send data to each other. All we need to do is modify our logging code to write to a FIFO, and at the same time read it from another process.

So one of the scripts included in my new toolkit does just this. Once launched, it clears the screen and starts reading the FIFO. This blocks until your app opens the FIFO for writing. The data is then written to the screen, and when your app quits and closes the file descriptor, the script stops reading the data. Stick it in an infinite while loop and you have an incredibly useful error console. No more printing log files!

Setting up your development environment every time you boot can be tedious. I’ve created a script to do the lifting for you automatically. All tools provided with the toolkit use a unified method of determining if your iPhoneOS device is connected, and allows you to mount it on your own if you please without errors.

All this and more will be released as alpha software in my next installment, so stay tuned!

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, …)

Subclassing
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
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()
window.setFrame_(rect.toTuple())

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!

The state of unsanctioned (read: jailbroken) iPhone toolchains is still very poor. There are currently two projects available to aid (not to create literally as this is not strictly legal because Apple-owned files are required) in creating a viable iPhone toolchain, but both are frought with problems and barely tested on the latest iPhone firmware and SDK (version 3.1.2). Even if (when?) this changes, issues will still arise for each new SDK/firmware release from Apple.

Luckily, thanks to saurik and crew, we have a Python port available from Cydia/Rock. Very little work has gone into discovering what it takes to write Python applications for the iPhone, and thus very little information is available. I hope to remedy that with this new series of posts. I will be adding new posts as soon as I have new information so more people can begin prototyping their apps in Python (and thus avoid the immediate need for a working Objective-C toolchain)

Getting Started with iPhone Python
To begin you will need the iPhone Python port which can be found in the iphone-python APT package, or just search for iPhone Python from Cydia, Rock, or Icy.

Once you’ve installed it and resprung, you will notice a new application called HelloPython. This application, like all jailbreak apps (and in fact all of Apple’s stock apps) are found in the /Applications directory in the root filesystem of your device. This directory is actually a symlink to /var/stash/Applications.XXX, so if you are interacting with your filesystem using Linux’s sshfs like I am, you will need to use the full name since the symlink will be broken in this situation.

All iPhone apps are OSX bundles. The bundle names end with ‘.app’, ie MobileSafari.app, MobileNotes.app etc. Each application bundle contains an Info.plist file which defines a number of properties of the application including the title shown in the Springboard, the bundle identifier, the filename of the executable to start when the application is launched etc.

HelloPython simply displays your contacts. There is another more advanced example Python application which was released to the iphone-python mailing list at telesphoreo called pyExamp. You can find it here (http://www.telesphoreo.org/pipermail/iphone-python/2009-February/000324.html).

Neither application is even close to useful, nor are they typical of a real application’s structure. They do however illustrate the PyObjC support which allows you to interface with the iPhone APIs. For more information on the iPhone APIs, visit http://developer.apple.com and sign up as an iPhone app developer if you haven’t already.

The first step in writing your app is to create a viable template in which to write and test the code. There is no iPhone simulator that can test your Python apps, the only way to do it is to put your app on your (jailbroken) iPhone and run it. The iPhone OS does not directly give you error messages, nor is there any way to view the output of your application directly when it is run from the Springboard. It is also not possible to start iPhone apps via the console. Erica Sadun’s “Erica Utilities” package features a ‘launch’ application which unfortunately does not work for OS 3.1.2. Running your app from SSH or MobileTerminal.app still has advantages: the app should launch completely normally, but it will not display anything (headless).

When an iPhone-Python app launches, typically the Springboard will run a BASH script (see the ‘HelloPython’ and ‘pyExamp’ scripts for examples), which launches Python and points it at your main application script (HelloPython.py or pyExamp.py). A UNIX developer’s first instinct for reading output from an app run would be to use ‘tee’ so that output is visible when run from the command line as well as logged to a file when run from Springboard. Unfortunately this will not work!! If you use ‘tee’ your applications will not launch. You will likely see the error ‘Failed to register com.company.appName with bootstrap server: Unknown error’ in your log. This is because of the redirection BASH does to send the app stdout/stderr to ‘tee’.

Normal redirection like ‘1>&2 2>/var/mobile/myApp.log’ will work just fine. Unfortunately this means you will have to display the log file to get the output, even when you run the app from the command line. My apps use a generic script which does not need to be modified when used in a new application, feel free to use and improve it:
#!/bin/bash
exec “$(dirname “$0”)”/Python -u “$(dirname “$0”)”/`basename $0`.py 2>&1 1>/var/mobile/`basename $0`.err.log

Important: Notice the ‘-u’ option to python. This causes Python to run in unbuffered mode. If you don’t do this, your app output may never get written to the log file (or even screen) when Python is killed or terminated abruptly. So if the script is called ‘MyApp’, then the log will be /var/mobile/MyApp.err.log.

This setup is sufficient for getting your program output. Some people prefer redirecting Python’s stdout from within Python itself like this:

sys.stdout = open(‘/var/mobile/myApp.log’, ‘w’)
sys.stderr = sys.stdout

However you will not see Python parse errors using this technique (obviously), so you would still need to use the first mechanism to capture parse/global errors which occur before the above code is even executed.

Before running your new application you will need to change the bundle identifier (and whatever else you want to change) in Info.plist. This is only needed to prevent the iPhone OS from thinking your app is already running (of course, not a problem unless you are backgrounding HelloPython for some odd reason).

With this info in mind, you should be able to make a copy of HelloPython.app or pyExamp.app and get it to run. Next you’ll have to actually write the application!

My next post will talk about the awkwardness of PyObjC and how to make iPhone-Python development smoother.

Dear Apple:

I understand that Wifi location services is the only method the iPod Touch can use to determine it’s location. I also understand that mapping the location of every access point in the US is a difficult, if untenable goal. In this age, this sort of huge effort is much improved by using crowd-sourcing. As a geek who loves organizing information, I would be honored to help Apple and Skyhook improve their access point maps, but this is very difficult because of the lack of effort on the part of Skyhook, and to a much lesser extent, yourselves.

I live in the upper peninsula of Michigan, and it is very clear that Skyhook has never sent their trucks through my area. My home access points, those I use at my university, the public APs found at grocery stores and coffee shops all provide no location information on my iPod Touch. What’s worse, is the location check actually spins for about 40 seconds before just giving up, instead of quickly recognizing that the AP is not mapped. Location-aware applications ask to check my location multiple times, near constantly, and some applications simply do not work if location detection fails.

To begin to remedy this situation I have begun submitting MAC addresses with location information directly to Skyhook but the process is very rough. Their web page for doing so is very inconvenient as it lacks any sort of location search, and instead requires you to zone into your location from a global view, which is error prone and time consuming. None of my submissions thus far (the first sent in 2 weeks ago) have been added to your database yet (and thus location-based apps DO NOT work at all for anyone in my area). There is no iPod app for submitting access points, and the iPod does not reveal the AP’s MAC address to me. Therefore, I must boot my laptop to find the MAC address of my current AP, which means using the Skyhook submission page on my iPod is pretty useless.

On top of that they have totally botched the Linux support for their Loki toolbar, meaning I must use ‘iwconfig’ to get my AP MAC and plug it into that terrible web page each and every time I want to submit a new AP. Mac OS X is a UNIX system like Linux, do you really want to work with a partner who offers a UNIX “installation script” that looks like this?

(the file is called “LokiPlugin_Installer.sh”):

— beginning of file —
Self Extracting Installer

installer_actions.sh
npplugin.tar

Running Installer

libnploki.so
loki_logo_dialog.png
Installation complete
— end of file —
Figure 1: This is what happens when you let a moron manage your Linux support

My university has one of the most advanced WiFi networks in the central United States. Intel frequently does WiFi-related internships here and just this year they launched a WiMax testing program. As you can imagine, there are literally hundreds of APs spread across our fairly large campus, which would be a massive amount of wonderful data for Skyhook and yourselves, and could offer very (VERY) fine grained location data to our students and staff.

I posit to you Apple, that you replace Skyhook Wireless as the location data provider for the iPod Touch for one which is more capable of managing the ends of the business that affect your customers. I know it is very possible for Apple to change location providers or completely drop the features with an OS update, and I suggest you do so. With the exception of the location services system, I have been incredibly impressed and happy with my new iPod Touch 3G, which makes the utter failure of such services even more in sharp contrast to Apple’s usual reputation for ridiculously good quality (I have *never* gotten a proper location).

Even if there are no viable databases you could adopt that would provide the scope that Skyhook already does, there is no reason you could not improve upon their reporting by integrating location submission into the iPod Maps application. There is no reason the dialog which says “Could not determine your location” couldn’t have a button which says “Map my current location”, which opens Maps and allows you to stick the pin on your current location, which is submitted to you, and eventually to Skyhook. Please AT LEAST make it possible to use Location-aware apps even when Skyhook inevitably fails to determine your location.

Thank you for your time. I hope you address this problem as soon as possible.

Sincerely, your devoted iTunes and iPod Touch customer,

William Lahti

(This letter was sent to Apple Feedback and Support shortly after posting this.)