Irrational Exuberance!

How to Use Selectors in PyObjC

August 19, 2008. Filed under pyobjc

Recently, I was working on a PyObjC project and I ran into a slightly complex situation. I wanted to schedule an action and run it at a later time, unless a specific condition was satisfied, in which case I wanted to invalidate the scheduled action.

Writing in raw Python I could have used the Timer class to accomplish this.

from random import random
from threading import Timer

def disp():
    print "Couldn't find in time."

timer = Timer(15.0,disp)
timer.start()
while 1:
    if random() > 0.9:
        timer.cancel()
        print "Found!"
        break

But my mind was rolling in Objective-C mode, so I immediately through of using NSTimer. The definition of the NSTimer method for creating a new instance looks like this1:

+scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:

No problem, we can just translate that using our handy PyObjC rules.

NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(delay,target,selector,userInfo,repeats)

Most of the arguments are self explanatory, but there was something I was unsure of: how do you create selectors in PyObjC? It turns out, pretty easily, but with a bit of magic.

import objc
class VendingMachine(object):
    def __init__(self,products):
        self.products = products
        self.timer = None
        self.reset_timer()

    def reset_timer(self):
        if self.timer is not None:
            self.timer.invalidate()
        s = objc.selector(self.advertise,signature='v@:')
        self.timer = NSTimer.scheduleTimerWithTimeInterval_target_selector_userInfo_repeats(30.0,self,s,None,True)

    def advertise(self):
        print "Don'cha wanna buy something?"

    def vend(self):
        self.reset_timer()
        print "Thanks for buying."

So that all makes sense, right? Except, perhaps, for one little thing that doesn't make any sense at all? If I may be so bold, the signature='v@:' part seems the slightest bit magical and even (dare I say) arbitrary. Fortunately, it's an easy magic trick to pick up.

The first part, the v indicates that the method will return void--i.e. it doesn't return anything, and the second part, @:, indicates that it is an instance method. Since my object isn't taking any parameters thats all there is to it.

But if the method was taking some parameters, thats easy too. The signature v@:@fi indicates that the method takes three signatures. @ represents an object, f represents a float, and i represents an integer. You can also use those same symbols instead of v for specifying the type of a returned object. For example, consider this Python method:

class MyClass(object):
    def my_method(self,x_int,y_int,z_obj):
        print x_int + y_int
        return z_obj

Creating the selector for that method in Python would look like this:

import objc
s = objc.selector(my_method,signature="@@:ii@")

Awkward, to be sure, but nothing insurmountable with our four magic symbols: v,@,i, and f.

Additional Sources

The places I scrounged for information--all of which were helpful but not quite comprehensive--were objc.selector and objc.signature at Jim Matthews Blog, some PyObjC documentation online, and the always helpful Python docstrings in the PyObjC code itself:

>>> import objc
>>> print objc.selector.__doc__
selector(function, [, selector] [, signature] [, isClassMethod=0]
    [, returnType] [, argumentTypes] [, isRequired=True]) -> selector

Return an Objective-C method from a function. The other arguments 
specify attributes of the Objective-C method.
etc...

All three may prove fruitful for those looking for more tidbits to assemble into a somewhat complete picture of using selectors in PyObjC.

Let me know if there are any questions or comments!


  1. Actually, there are a couple of different class methods to choose from, but I picked the easiest one to work with.