Fun with reference counts

I’m having some type 2 fun with PyObjC while bringing its updates of reference counts closer to the patterns used by ARC.

Objective-C using reference counting for memory management, similar to CPython. In modern code bases (and when using Swift) the compiler does this for you (“ARC”), but PyObjC still uses manual updates of the reference counts.

PyObjC used CFRetain and CFRelease to increase and decrease the reference counts, instead of the more common [value retain] and [value release]. This made more sense during the time when Apple experimented with garbage collection years ago (basically before iPhone).

It turns out that mixing the two can cause crashes in Objective-C code, e.g:

NSURL* url = [NSURL alloc];  // new reference, caller must release
CFRelease(url);

Bisecting the PyObjC test suite to pinpoint the problem was annoyingly hard, especially when I fully expected that the root cause was a bug in my update instead of this. Luckily the fix was a lot easier: PyObjC will use retain and release methods to update reference counts going forward (starting with the upcoming 11.1 release).

A second issue found while testing the 11.1 release. The following code will crash hard when compiled using ARC:

#import <Foundation/Foundation.h>

int main(void)
{
     NSOutputStream* stream;

      stream = [NSOutputStream alloc];
      stream = [stream initToMemory];

      NSLog(@"%@", stream);
}

This is split calls to alloc and initToMemory are effectively what happens when using NSOutputStream.alloc().initToMemory() in Python. The workaround in PyObjC’s test suite is to create the stream using NSOutputStream.outputStreamToMemory(), working around Apple’s incorrect handling of reference counts.

This appears to be a genuine bug in macOS, filed as FB17759654.

Ronald Oussoren @ronaldoussoren