How to Use Selectors in 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()
<span class="k">def</span> <span class="nf">reset_timer</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">timer</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">timer</span><span class="o">.</span><span class="n">invalidate</span><span class="p">()</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">objc</span><span class="o">.</span><span class="n">selector</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">advertise</span><span class="p">,</span><span class="n">signature</span><span class="o">=</span><span class="s">'v@:'</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">timer</span> <span class="o">=</span> <span class="n">NSTimer</span><span class="o">.</span><span class="n">scheduleTimerWithTimeInterval_target_selector_userInfo_repeats</span><span class="p">(</span><span class="mf">30.0</span><span class="p">,</span><span class="bp">self</span><span class="p">,</span><span class="n">s</span><span class="p">,</span><span class="bp">None</span><span class="p">,</span><span class="bp">True</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">advertise</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">print</span> <span class="s">"Don'cha wanna buy something?"</span>
<span class="k">def</span> <span class="nf">vend</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">reset_timer</span><span class="p">()</span>
<span class="k">print</span> <span class="s">"Thanks for buying."</span>
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!
Actually, there are a couple of different class methods to choose from, but I picked the easiest one to work with.↩