An introduction to PyObjC

Contents

Preface

PyObjC is a bridge between Python and Objective-C. It allows you to write Python scripts that use and extend existing Objective-C class libraries, most importantly the Cocoa libraries by Apple.

This document describes how to use Objective-C class libraries from Python scripts and how to interpret the documentation of those libraries, from the point of view of a Python programmer.

Objective-C for PyObjC users

It is necessary to understand a little bit of Objective-C to use PyObjC, this helps you to better understand the class libraries and makes it easier to read (and translate) example code.

Objective-C is an object-oriented programming language that is an extension of C and borrows heavily from Smalltalk. It features single inheritance with (in theory) multiple root classes and dynamic dispatch of methods. This is basicly the same as Python with single inheritance.

An important difference between Python and Objective-C is that the latter is not a pure object-oriented language. Some values are not objects, but values of plain C types, such as int and double. These basic C types can also be used as the types of arguments and the return value of methods.

Object allocation and initialization are explicit and seperate actions in Objective-C. The former is done by the class-method alloc, while the latter is done by instance-methods whose name customarily starts with init.

Objective-C code looks just like plain C code, with some easily recognizable extensions for the Object-Oriented parts of the language. And example class declaration (usually found in .h files) and implementation (usually found in .m files) are listed below). Class declarations are easily recognized as blocks of code between @interface and @end, and simularly the implementation is between @implementation and @end. Calling methods is done using expressions enclosed with brackets (name?), e.g. [foo method]. This is the same as foo.method() in Python.

A class declaration:

@interface MYClass : MySuperClass
{
   id  anInstanceVariable;
   int anotherInstanceVariable;
}

+aClassMethod;

-(int)anInstanceMethodWithArg1:arg1 andArg2:(BOOL)arg2;
@end

A class implemenation:

@implementation MYClass

+aClassMethod
{
     id res = [[MYClass alloc] init];
     return res;
}

-(int)anInstanceMethodWithArg1:arg1 andArg2:(BOOL)arg2
{
     int res;

     if (arg2) {
             res = [self fooWith:arg1];
     } else {
             res = [arg1 bar];
     }
}

@end

Objective-C also features exceptions, but as those are mostly used for disaster recovery and not for normal error handling you won't see them very often in example code. The The Objective-C Programming Language if you want to know more about exceptions in Objective-C.

One thing to keep in mind when translating Objective-C snippets to python is that it is valid to call methods on nil (that is the NULL pointer). Those method calls are ignored by the runtime. The value nil is represented in Python by the None, this means that calls to non-existing methods are not ignored but will raise AttributeError.

For more information about Objective-C see:

Overview of the bridge

Classes

Objective-C classes are visible as (new-style) Python classes and can be subclassed just like normal Python classes. All the usual introspection mechanism work as well, as do __slots__ and descriptors. The major differences between normal Python classes and Objective-C classes are the way you create instances and the fact that Objective-C methods have odd names.

You can use multiple inheritance with Objective-C classes, as long as the Objetive-C is the first base-class and there is only one Objective-C base-class. E.g. it is not possible to subclass from the Objective-C classes at the same time. Multiple inheritance should also not be used to mix-in different implementations for Objective-C methods, that will not work and you won't get errors about this.

Another thing to keep in mind is that the names of Objective-C classes must be unique, without taking modules into account. That is, it is not possible to have two modules that define a class with the same name. If you write classes that will be used outside of a single project it is customary to pick a (short) prefix and stick that in front of all class names, e.g. Apple NS as the prefix in the Cocoa libraries.

As described in Objective-C for PyObjC users the creation of Objective-C objects is a two-stage process. You first call the class method alloc, and then call some variation of init to initialize the objects. The newly created object is the result of the call to init. Most classes have convienence class methods that combine the calls to alloc and init.

Methods and functions

Objective-C methods are bridged to Python callables. Because Objective-C method names can contain colons it is necessary to translate methods names. The rules for translation are:

Wrapped/bridged methods (and functions) have the same number of arguments as the corresponding Objective-C method or function, unless otherwise noted in the documentation (Notes on supported APIs and classes on MacOS X for Cocoa on MacOS X).

One group of exceptions to this rule can be described in a global way. Some methods and functions have pointers as arguments, specifically pointers to a single value that is passed in and/or out of the function. These arguments are sometimes called pass by reference arguments, and can be subdived into three types of arguments: in arguments are used to pass data to the function, out arguments are used to pass data from the function (e.g. and additional return value) and inout arguments are a combination of the two.

The in and inout arguments for a method are also present in the Python interface for that method (or function). In python the value passed to the function is a "normal" argument. Out arguments are not present in the argument list of the Python function.

If a function (or method) has one or more output arguments (out or inout) the output values are returned as part of the return value of the method. That is, the return value of the function is a tuple containing the return value of the C function (or method), followed by the values of the out in inout arguments in the order the are present in the argument list. If the C function (or method) has return type void, the tuple contains only the output arguments. As a final complication, methods with a single output argument and return type void, have the value of the output argument as the return value (e.g. not a tuple containing the return value).

The rules for pass by reference arguments may look quite complicated, but it turns out this is very straightforward when working with them.

As an example of a method with two output arguments, NSMatrix has a method named getNumberOfRows_columns_ with the following signature:

(void)getNumberOfRows:(int *)rowCount columns:(int *)columnCount

You use this method in python like this:

rowCount, columnCount = matrix.getNumberOfRows_columns_()

When you define methods in a subclass of an Objective-C class, the bridge has to tell the Objective-C runtime what the signature of those methods is. The basic rule is that all arguments as well as the return value are objects (just like with normal Python methods). The bridge will automaticly pick a better signature when it has more information available. Specifically, if you overide an existing method the bridge will assume you want to use the same method signature. And furthermore, if you implement a method in an (informal) protocol known to the bridge it will use the signature from the corresponding method in that signature.

The end result is that you almost never have to add information about the signature of methods. The only known case where you have to tell the bridge about the signature of a method is the call-back method for sheets. You can use the function PyObjCTools.AppHelper.endSheetMethod to create an object that contains the right information. This function is used like staticmethod and classmethod (as introduced in Python 2.2).

For complete control of the mapping to Objective-C you can use the function objc.selector. See the documentation of the objc module for the arguments you can use with this function. It is normally used like this:

class MyObject (NSObject):
        def someMethod_(self, arg):
                pass

        someMethod_ = objc.selector(someMethod_, ...)

Reference counting

The Cocoa libraries, and most (if not all) other class libraries for Objective-C use explicit reference counting to manage memory. The methods retain, release and autorelease are used to manage these reference counts. You won't have to manage reference counts in Python, the bridge does all that work for you.

The only reasons reference counts are mentioned at all are to tell you about ignoring them, and more importantly to introduce you to some issues w.r.t. reference counting.

It turns out that Cocoa uses a primitive form of weak references. Those are not true weak references as in Python, but use-cases where an object stores a reference to another object without increasing the reference count for that other object. The bridge cannot solve the issues this introduces for you, which means that you get hard crashes when you're not carefull when dealing with those weak references.

The basic rule to deal with weak references is: make sure objects stays alive as long as someone might have a weak reference to them. Due to the way the bridge works, this means that you must make sure that you don't create weak references from Objective-C to a plain Python object. The Python object stays alive, but the proxy object as seen by the Objective-C code is actually an autoreleased object that will be cleaned up unless the Objective-C code increases its reference count.

The document Notes on supported APIs and classes on MacOS X contains information about classes that work with weak references. The most important are notification centers and NSOutlineView, to be exact: the outline view stores weak references to the objects return by the method outlineView:child:ofItem: of its data source. The easiest way to avoid crashes with outline views is to make sure that you model for the view uses subclasses of NSObject to represent the nodes in the outline view.

Another gotcha is when you're manually allocating and assigning delegate(-like) objects: most of the time obj.setDelegate_() will not retain the delegate, so you must keep a reference manually.

(Informal) protocols

Cocoa defines a number of formal and informal protocols that specify methods that should be implemented by a class if it is to be used in a specific role, such as the data source for an NSTableView.

Those protocols are represented by instances of objc.informal_protocol. The only ones that have to care about these objects are the maintainers of wrappers around Objective-C frameworks: they have to keep these protocol wrappers up-to-date.

PyObjC will automaticly use the information in the informal_protocol objects to add the right method signatures to methods, and to warn about classes that partially implement a protocol.

Cocoa for Python programmers

Cocoa frameworks are mapped onto Python packages with the same name, that is the classes, constants and functioins from the AppKit framework are available after you import AppKit in your Python script.

These helper modules contain only functions, constants and classes that wrap items in the corresponding framework. All utility functions and classes are located in the PyObjCTools package and objc module. Note that it is possible to use pydoc (or the help()) function with the framework wrappers, but that this is not very usefull for the entire module due to the size of these modules.

This makes it easier to find documentation for an item: if you import it from the wrapper module for an Objective-C framework the documentation for that item can be found in the documentation for the framework, otherwise the item is documented in the PyObjC documentation.

The module PyObjCTools.NibClassBuilder can be used to make working with NIB files more convenient. This module can be used to extract information about classes from NIB files, both as a standalone tool generating source code and during runtime. See the online documentation for this module for more information.

PyObjC includes a number of examples that show how to use Cocoa from Python. The PyObjC Example index contains an overview of those examples.

More information on Cocoa programming can be found at:

Notes on specific tasks

Working with threads

When you create a thread and want to use PyObjC from that thread you will have to create an NSAutoreleasePool in that thread and clean it up when you're done. The easiest way to that is to create an instance of that class bound to a local variable. If the thread is long-lived you may want to arrange for recycling the pool once in a while.

There are some limitiation w.r.t. threading. You cannot use NSThread to create new threads, but must use the python primitives instead.

You must also make sure that Objective-C only makes calls to Python from a thread that owns the Python GIL (that's also the reason for not being able to use NSThread to create new threads). This restriction will be lifted in a future version of PyObjC (at least when using Python 2.3).

Finalizers

In Python you can use the method __del__ to clean up resources when your object is garbage collected. In Objective-C/Cocoa this is done with a method named dealloc.

In PyObjC you should always use the __del__ method, the dealloc method can safely be ignored and the bridge will complain when you try to override this method.

Building applications

There are two different ways to build applications with PyObjC. There are no major advantages to using either one of them, use the one that is most convenient to you.

"Pure Python" : buildapp.py

PyObjC includes a copy of the bundlebuilder module. This module will be part of the Python 2.3 MacPython release and offers a way to build distutils-style scripts for building (standalone) applications.

An example buildapp.py script:

from bundlebuilder import buildapp
buildapp(
        name = 'iClass',
        mainprogram = "main.py",
        resources = ["English.lproj", "datasource.py" ],
        nibname = "MainMenu",
)   

During development you typically invoke it from the command line like this:

python buildapp.py --link build

This will build an application bundle in a folder named build in the current folder. The --link option tells bundlebuilder to add symbolic links to the application bundle instead of copies of your source and resource files, allowing you to edit them without having to rebuild the application. To build a standalone application, either use --standalone or --semi-standalone. The latter will put all used modules that are not in Python's standard library into the application bundle. The result will still depend on an installed Python, but yields a relatively compact application. --standalone will cause bundlebuilder to include everything needed into the app bundle, including the entire Python runtime. This is useful if you're using a different version of Python that the one that comes with MacOSX 10.2, or if you fear that a future version of OSX may come with an incompatible Python version.

The online documentation for bundlebuilder contains more information on building buildapp.py scripts and how to invoke them. There are plenty of example buildapp.py scripts in the various Examples subfolders.

"IDE approach" : Project builder

PyObjC includes a number of Project Builder templates that can be used to build (standalone) applications. Those templates are used like any other Project Builder template. The only non-obvious detail is that you have to add your sources as resources, but Project Builder usually does the right thing when you add a new file.

The templates will build an application that makes use of the installed copy /usr/bin/python (e.g. the one shipped by Apple in MacOS X 10.2) and will copy the PyObjC modules into the application bundle. This means that this application bundle should be useable on any MacOS X 10.2 system.

See the documentation for the templates for more details.

MacOS X 10.3 seems to ship with Python 2.3 as /usr/bin/python. This means that standalone applications build using the Project Builder templates will start to complain about Python ABI versions in the Console when you run them on MacOS X 10.3. These are harmless messages caused by the extension modules in PyObjC.