PyObjC learnings

July 26, 2007

Lesson #1: Don’t forget to return self when you override init. Otherwise nothing works, but it isn’t obvious why.

Lesson #2: Initialize instance variables in init. I started out trying to avoid overriding init whenever possible. Perhaps it was because I was gunshy after learning Lesson #1 the hard way. Also, I don’t like the two lines of boilerplate that begin every overridden init:

class MyObject(NSObject):
    def init(self):
        self = super(MyObject, self).init()
        if self is None: return None

One of the great things about Python is its terseness, and having to type that stuff every time is annoying. I think that if I knew enough Python I could write a function decorator to handle the obligatory call to superclass, but then I’d have to add the decorator module to the project and import it — not a big savings in typing.

So when I needed to initialize an instance variable I tried to avoid overriding init, and instead would perform the initialization in the class definition:

class MyObject(NSObject):
    _array = []
    _lastIndex = 0
   ...etc...

That way instance methods could refer to self.instance-variable-name and get the initialized class attribute if no per-instance value had yet been set. Having that series of assignments at the top of the class definition also served to gather together all the instance variable names I planned to use, rather than having them scattered through the code.

But experienced Python programmers will immediately spot the problem. Objects assigned to class attributes are shared between all the instances of the class, and so changes made to mutable objects by one instance are seen by all the others. If instance A added an item to self._array, instance B’s self._array would have it as well.

That was not at all what I wanted. So I’ve gone back to overriding init whenever I need to initialize instance variables.


iPhone and the Neo Ion

July 26, 2007

I finally got around to trying the iPhone with the iPod adapter in our other car, a Neo Ion from MP3YourCar. The Ion is wired directly into the stereo’s CD changer port, so there’s just one cable and the audio quality is great. But, like the Belkin, the Ion charges the iPhone but does not route audio to the car stereo. And (unlike the Belkin), the cable to the iPhone is too short to allow use of the phone (except as a speakerphone) while it’s plugged in. The short cable can be a hassle even with an iPod (e.g. when someone in the back seat wants to choose the music), and so far I haven’t found any iPod dock extension cables.

Apple was farsighted to standardize on the 30-pin dock connector for multiple generations of iPods, and smart to use it in the iPhone, so it’s a shame that the compatibility with existing adapters is only partial.


objc.selector and objc.signature

July 12, 2007

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 insertObject:in<Key>AtIndex: and removeObjectFrom<Key>AtIndex:, where <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 selector and 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_ with:

insertObject_inEmployeesAtIndex_ = objc.selector(
insertObject_inEmployeesAtIndex_,
signature = 'v@:@I'
)

and to follow the definition of removeObjectFromEmployeesAtIndex_ with:

removeObjectFromEmployeesAtIndex_ = objc.selector(
removeObjectFromEmployeesAtIndex_,
signature = 'v@:I'
)

In Python 2.4 it’s a bit simpler; you can preceed the definition of insertObject_inEmployeesAtIndex_ with:

@objc.signature('v@:I')

and the definition of removeObjectFromEmployeesAtIndex_ with:

@objc.signature('v@:I')

objc.selector or 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.


The iPhone and the original Macintosh

July 12, 2007

John Gruber made a comment in the second episode of The Talk Show podcast to the effect that the iPhone merits comparison with the original Macintosh 128K. That got me thinking about the two products, and their introductions.

I was in college in 1984, and while there was some hype generated by the “1984″ Super Bowl commercial, I don’t recall knowing anyone who was looking forward to January 24 the way I was. On the big day I picked up that week’s copy of InfoWorld, with Steve Jobs on the cover, and the first issue of MacWorld, at Out-of-Town News in Harvard Square, and headed into the Harvard Coop to see the machines on display. They had a couple set up, running MacPaint and MacWrite, with ImageWriters you could use to print out your doodles. There was no hoopla, no crowd, and at $2,499 (without peripherals) certainly no worries about running out of stock. All the same, the machines were magical, utterly unlike the personal computer state of the art.

The two things that struck me most immediately about the Mac were the screen, and the mode of interaction. At a time when other computers used green (or amber)-on-black text, with big fuzzy pixels, the Mac 128K used sharp black-on-white. And while you communicated with other computers through a layer of abstract commands, typing “copy letter.txt b:” because you’d learned that command for backing up a file to a different disk, on a Mac you moved your hand to actually pick up that file, and drop it where you wanted it to go. It was the difference between typing directions and driving a car.

Of course these are also the two things that strike you first about an iPhone. The screen is so sharp that it has nearly as many pixels as the Mac 128K, squeezed into a space that fits in a pocket. And again, the interaction is direct: you flick photos, pinch maps, and tap on phone numbers, instead of pressing some button that’s arbitrarily labeled with the function you want.

A dozen years after the Mac 128K shipped virtually all personal computers worked like a Mac. I’m not sure that the iPhone will be as sweepingly successful a template: by 2019 we may have moved on to phones that we wear like glasses and interact with by voice command, or something similarly futuristic. But in the meantime I expect to see more and more iPhone-like phones, from Apple and others.

By the way, it is easy to forget how primitive the personal computers of 1984 were when the Mac debuted. For a look back, check out James Fallows’ excellent Living with a Computer. Originally published just 18 months before the Mac was released, it portrays a now almost unimaginable world. Tellingly, Apple is the only manufacturer mentioned that still makes personal computers. The rest — Tandy, Processor Technology, DEC, Heath, Atari, Commodore, Victor, Vector, Wang, Sinclair, North Star, Xerox, Osborne, and even IBM — either no longer exist or have fled the business.


Advantage: iPhone Google Maps

July 12, 2007

In planning out-of-town business meetings I’ve found myself looking up hotels on my iPhone instead of using the MacBook Pro in front of me. Typing the name of the town where I’ll be is definitely slower on the iPhone, but the other steps — calling the hotel to ask about meeting rooms, and storing the hotel address, phone number and URL so I’ll have them later — only take a couple finger taps. The iPhone does much less than a laptop, but sometimes the few things it does are the exact things you want, with the result that you can do them more directly.


iPhone accessory compatibility watch

July 4, 2007

My favorite iPod car charger is the Belkin Auto Kit for iPod w/ Dock Connector. It keeps the iPod charged, pauses playback when I turn off the car, and has a line-out for iPod audio so I can use a cassette adapter without a second cable running from the car dashboard to the iPod. Unfortunately, the Belkin only half-works with the iPhone: the iPhone charges, but its audio is routed to the iPhone speakers rather than out the dock connector. I’ll be keeping an eye out for an iPhone-compatible replacement.

For more on iPhone accessory compatibility, see MacWorld.


The iPhone Internet, better than the regular Internet

July 3, 2007

YouTube on the iPhone is hampered (temporarily, we’re told) by a smaller selection of videos, but the videos that are there look strikingly better than the same videos viewed on a computer at YouTube’s web site. [Provided that the iPhone is on WiFi; on EDGE it apparently uses a crummier, lower bit rate stream]. I am curious how much of that is due to the use of H.264 (instead of Flash Video) on the iPhone, and how much is due to the iPhone’s higher resolution screen. I wonder if there is a way to access the H.264 versions of YouTube videos from a desktop web browser….

One thing that helped make YouTube popular was the ease of embedding videos into web pages and blog posts. Sadly, when you run across one of these in iPhone Safari you just get the you-don’t-have-Flash-installed missing plugin icon. It’d be slick if iPhone Safari instead put in a link that brought up the video in the iPhone YouTube app.


Google Maps vs. Multitouch scrolling

July 3, 2007

I love the drag-two-fingers-on-the-trackpad-to-scroll feature of my Mac Book Pro — it’s been a sneak preview of the multitouch user interface on the iPhone, very natural and convenient. But I’m always surprised at what happens when I use it in Google Maps — the map zooms instead of scrolling. The zoom operation is fairly slow, and there’s no quick way to undo it, so I feel frustrated every time this happens.

No doubt when Google first mapped mouse scroll wheels to the zoom feature it seemed like a clever trick: scrolling was already handled by click and drag, and this way you could adjust the zoom without moving the mouse. But the trackpad scrolling gesture — click and drag with two fingers — does not feel like a zoom, it feels like a scroll, and it is a scroll everywhere else in the UI.

Fortunately, finger dragging works as expected in Google Maps on the iPhone (with one finger or two). As more people become familiar with multitouch interfaces, on the iPhone and other devices, I expect that more people will stumble on the maps.google.com zoom feature, and perhaps Google will reconsider it.