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:
# 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 """
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!