Memory management for Objective-C instances¶
Reference counting in Objective-C¶
Reference counting works differently in Objective-C compared to Python. Python
will automatically track where variables are referenced and free memory when
the reference count drops to zero whereas Objective-C uses explicit reference
counting to manage memory. The methods retain
, release
and
autorelease
are used to increase and decrease the reference counts as
described in the Apple developer documentation.
When enabling automatic reference counting (ARC), the appropriate calls for
memory management will be inserted for you at compile-time. However, since
Rubicon Objective-C operates at runtime, it cannot make use of ARC.
Reference management in Rubicon¶
In most cases, you won’t have to manage reference counts in Python, Rubicon
Objective-C will do that work for you. It does so by calling retain
on an
object when Rubicon creates a ObjCInstance
for it on the Python side, and calling
autorelease
when the ObjCInstance
is garbage collected in Python. Retaining
the object ensures it is not deallocated while it is still referenced from Python
and releasing it again on __del__
ensures that we do not leak memory.
The only exception to this is when you create an object – which is always done through methods starting with “alloc”, “new”, “copy”, or “mutableCopy”. Rubicon does not explicitly retain such objects because we own objects created by us, but Rubicon does autorelease them when the Python wrapper is garbage collected.
Rubicon Objective-C will not keep track if you additionally manually retain
an
object. You will be responsible to insert appropriate release
or autorelease
calls yourself to prevent leaking memory.
Weak references in Objective-C¶
You will need to pay attention to reference counting in case of weak
references. In Objective-C, as in Python, creating a weak reference means that
the reference count of the object is not incremented and the object will be
deallocated when no strong references remain. Any weak references to the object
are then set to nil
.
Some Objective-C objects store references to other objects as a weak reference.
Such properties will be declared in the Apple developer documentation as
“@property(weak)” or “@property(assign)”. This is commonly the case for
delegates. For example, in the code below, the NSOutlineView
only stores a
weak reference to the object which is assigned to its delegate property:
from rubicon.objc import NSObject, ObjCClass
from rubicon.objc.runtime import load_library
app_kit = load_library("AppKit")
NSOutlineView = ObjCClass("NSOutlineView")
outline_view = NSOutlineView.alloc().init()
delegate = NSObject.alloc().init()
outline_view.delegate = delegate
You will need to keep a reference to the Python variable delegate
so that
the corresponding Objective-C instance does not get deallocated.
Reference cycles in Objective-C¶
Python has a garbage collector which detects references cycles and frees objects in such cycles if no other references remain. Cyclical references can be useful in a number of cases, for instance to refer to a “parent” of an instance, and Python makes life easier by properly freeing such references. For example:
class TreeNode:
def __init__(self, val):
self.val = val
self.parent = None
self.children = []
root = TreeNode("/home")
child = TreeNode("/Documents")
child.parent = root
root.children.append(child)
# This will free both root and child on
# the next garbage collection cycle:
del root
del child
Similar code in Objective-C will lead to memory leaks. This also holds for Objective-C instances created through Rubicon Objective-C since Python’s garbage collector is unable to detect reference cycles on the Objective-C side. If you are writing code which would lead to reference cycles, consider storing objects as weak references instead. The above code would be written as follows when using Objective-C classes:
from rubicon.objc import NSObject, NSMutableArray
from rubicon.objc.api import objc_property, objc_method
class TreeNode(NSObject):
val = objc_property()
children = objc_property()
parent = objc_property(weak=True)
@objc_method
def initWithValue_(self, val):
self.val = val
self.children = NSMutableArray.new()
return self
root = TreeNode.alloc().initWithValue("/home")
child = TreeNode.alloc().initWithValue("/Documents")
child.parent = root
root.children.addObject(child)
# This will free both root and child:
del root
del child