Bob Ippolito (@etrepum) on Python, Erlang, JavaScript, etc.
«

PyObjC First Steps

»

I've been neglecting Python a bit lately, but Ronald is going to cut a release of PyObjC 1.3.7 very soon, hopefully later this week. PyObjC 1.3.7 fixes a few bugs (particularly with Xcode 2.1 templates), has a more complete wrapping of Mac OS X 10.4 system frameworks (DiscRecording, SenTestingKit, SecurityFoundation), and adds a new TinyURLService example (which converts URLs on the pasteboard to tinyurl.com equivalents).

There's also a swath of new "First Steps" documentation in the PyObjC intro, which I wrote up earlier today:

First Steps

When dealing with the Objective-C runtime, there are certain patterns you need to learn when writing Python code. If you're not already an Objective-C programmer, some of them will seem strange or even "un-pythonic" at first. However, you will get used to it, and the way that PyObjC works is quite compliant with the Zen of Python (import this). In fact, Ronald is Dutch ;)

With no further ado, here are the three most important things you must know before embarking on any PyObjC voyage:

Underscores, and lots of them

Objective-C objects communicate with each other by sending messages. The syntax for messages is somewhere in-between Python's positional and keyword arguments. Specificlaly, Objective-C message dispatch uses positional arguments, but parts of the message name (called "selector" in Objective-C terminology) are interleaved with the arguments.

An Objective-C message looks like this:

[someObject doSomething:arg1 withSomethingElse:arg2];

The selector (message name) for the above snippet is this (note the colons):

doSomething:withSomethingElse:

In order to have a lossless and unambiguous translation between Objective-C messages and Python methods, the Python method name equivalent is simply the selector with colons replaced by underscores. Since each colon in an Objective-C selector is a placeholder for an argument, the number of underscores in the PyObjC-ified method name is the number of arguments that should be given.

The PyObjC translation of the above selector is (note the underscores):

doSomething_withSomethingElse_

The message dispatch, translated to PyObjC, looks like this:

someObject.doSomething_withSomethingElse_(arg1, arg2)

Methods that take one argument will have a trailing underscore.

It may take a little while to get used to, but PyObjC does not ever rename selectors. The trailing underscore will seem strange at first, especially for cases like this:

# note the trailing underscore
someObject.setValue_(aValue)

There are a few additional rules regarding message dispatch, see the Overview of the bridge for the complete rundown.

Two-phase instantiation

Objective-C, being a low-level runtime, separates the two concepts required to instantiate an object.

allocation:
Reserve a chunk of memory large enough to hold the new object, and make sure that all of its declared instance variables are set to "zero" (this means nil pointers to objects, 0 for integers, etc.).
initialization:
Fill in the blank slate allocated by the allocation phase.

In Objective-C, the convention is for allocation to be performed by a class method called alloc, and initialization is done with method beginning with the word init. For example, here is the syntax for instantiating an NSObject:

myObject = NSObject.alloc().init()

And here is an example for creating an NSData instance given a few bytes:

myData = NSData.alloc().initWithBytes_length_('the bytes', 9)

You must also follow this convention when subclassing Objective-C classes. When initializing, an object must always (directly or indirectly) call the designated initializer of its super. The designated initializer is the "most basic" initializer through which all initialization eventually ends up. The designated initializer for NSObject is init. To find the designated initializer for other classes, consult the documentation for that class. Here is an example of an NSObject subclass with a customized initialization phase:

class MyClass(NSObject):

    def init(self):
        """
        Designated initializer for MyClass
        """
        # ALWAYS call the super's designated initializer.
        # Also, make sure to re-bind "self" just in case it
        # returns something else!
        self = super(MyClass, self).init()

        self.myVariable = 10

        # Unlike Python's __init__, initializers MUST return self,
        # because they are allowed to return any object!
        return self

class MyOtherClass(MyClass):

    def initWithOtherVariable_(self, otherVariable):
        """
        Designated initializer for MyOtherClass
        """
        self = super(MyOtherClass, self).init()
        self.otherVariable = otherVariable
        return self

myInstance = MyClass.alloc().init()
myOtherInstance = MyOtherClass.alloc().initWithOtherVariable_(20)

Many Objective-C classes provide class methods that perform two-phase instantiation for you in one step. Several examples of this are:

# This is equivalent to:
#
#   myObject = NSObject.alloc().init()
#
myObject = NSObject.new()

# This is equivalent to:
#
#   myDict = NSDictionary.alloc().init()
#
myDict = NSDictionary.dictionary()

# This is equivalent to:
#
#   myString = NSString.alloc().initWithString_(u'my string')
#
myString = NSString.stringWithString_(u'my string')

Objective-C uses accessors everywhere

Unlike Python, Objective-C convention says to use accessors rather than directly accessing instance variables of other objects. This means that in order to access an instance variable value of an object valueContainer you will have to use the following syntax:

# Getting
#
# notice the method call
#
myValue = valueContainer.value()

# Setting
#
# notice the naming convention and trailing underscore
#
valueContainer.setValue_(myNewValue)

When writing your own classes from Python, this is a bit harder since Python only has one namespace for all attributes, even methods. If you choose to implement accessors from Python, then you will have to name the instance variable something else:

class MyValueHolder(NSObject):

    def initWithValue_(self, value):
        self = super(MyValueHolder, self).init()
        # It's recommended not to use typical Python convention here,
        # as instance variables prefixed with underscores are reserved
        # by the Objective-C runtime.  It still works if you use
        # underscores, however.
        self.ivar_value = value
        return self

    def value(self):
        return self.ivar_value

    def setValue_(self, value):
        self.ivar_value = value

It's also possible to use Key-Value Coding in some cases, which eliminates the need for writing most accessors, but only in scenarios where the rest of the code is using it.

blog comments powered by Disqus