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. A message is the equivalent of an instance method in Python. The syntax for messages is somewhere in-between Python's positional and keyword arguments. Specifically, Objective-C message dispatch uses positional arguments, but parts of the message name (called the "selector" in Objective-C terminology) are interleaved with the arguments.
For example, this Objective-C code:
[aMutableArray addObject:@"constant string"];
is equivalent in intent to the following in Python:
Objective-C messages have three components: a target, a selector, and zero or more arguments. The target, aMutableArray in the example above, is the object or class receiving the message. The selector, addObject: uniquely identifies the kind of message that is being sent. Finally, the arguments, @"constant string" are used by the implementation of the method upon receipt of the message. The syntax of Objective-C message dispatch is deceptively similar to keyword arguments in Python, but they are actually quite different. Objective-C messages cannot have default arguments, and all arguments are passed in a specific order. The components of a selector may not be reordered. Syntactically, one argument must be interleaved at every colon in the selector. For the message:
[anArray indexOfObject:someObject inRange:someRange]
the target is anArray, the selector is``indexOfObject:inRange:`` (note the colons), and the arguments are someObject and someRange.
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):
The whole message dispatch, translated to PyObjC, looks like this:
Objective-C messages that take one argument will translate into a Python method with 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.
In Python, objects are usually instantiated by 'calling' the Class:
# Create a new MyClass object x = MyClass()
will create a new instance of a MyClass() object and assign it to the variable x. If the class (or its superclass) defines an __init__ method, that method will be automatically invoked. Traditionally, this is where the object's data attributes are set to their initial values.
Objective-C, being a low-level runtime, separates the two concepts required to instantiate an object into allocation and initialization.
- 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.).
- 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 by a method beginning with the word init. For example, here is the Objective-C syntax for instantiating an NSObject:
myObject = [NSObject alloc]; [myObject init]; /* which is almost always shortened to */ myObject = [[NSObject alloc] init];
In Python, therefore, you create an NSObject like this:
myObject = NSObject.alloc().init()
And here is an Objective-C example for creating an NSData instance given a few bytes:
myData = [NSData initWithBytes: @"the bytes" length: 9]
and its Python translation:
myData = NSData.alloc().initWithBytes_length_('the bytes', 9)
If you forget to use alloc() and init(), and try to create an NSObject as you would create a Python object, you will get an error:
>>> myObject = NSObject() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Use class methods to instantiate new Objective-C objects
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 a Python 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, or even None! self = super(MyClass, self).init() if self is None: return None 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() if self is None: return None 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 (translated to Python) 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 requires the use of accessor methods to access instance variables of other objects Instance variables are not accessed directly. So to access an instance variable``value`` of an Objective-C class 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:
- 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.my_value = value return self
- def value(self):
- return self.my_value
- def setValue_(self, value):
- self.my_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.