[Openmcl-devel] cocoa memory management best practices?
plkrueger at comcast.net
Fri Sep 24 20:17:31 UTC 2010
I must admit that I've had a hard time figuring out the correct approach to take for Ralph's 2a/2b case, but here's the way I now think about it. Comments and corrections always appreciated.
When an instance of a class defined in Lisp that derives from an Objective-C class is gc'ed, all that means is that there are no more lisp references to it. There may still be references from other Objective-C objects that Lisp does not manage or know about. Therefore, it would be improper for the GC to physically deallocate an Objective-C object; and in fact in the tests I've run, it does not do so.
So it would also not be proper to call ccl::terminate-when-unreachable on such an instance and release values in slots for that object in a ccl::terminate method that is called during GC unless you are absolutely sure that there are no non-Lisp references to the object. And even in that case, the object itself will not be deallocated, unless it is released somewhere, so that should also be done in that terminate method. If you're not absolutely sure that there are only Lisp references to the object, then the dealloc method for the object is the proper place to release Objective-C objects in slots (of any type, not just :foreign-type).
An alternative choice for memory management for objects that might be referenced from both Lisp and other Objective-C objects is to use the ccl::terminate functionality. If the object is created via a make-instance call within Lisp, then its reference count is effectively 1. Rather than doing a #/retain for each assignment of that object within Lisp, we will simply use that initial reference count to indicate that some Lisp object references it and decrement that count when the object is GC'ed. Any objective-C objects to which this instance is passed as an argument may choose to retain and release it as well, but we don't need to be concerned about that. This might be implemented by something like the following:
(defclass my-class (ns:ns-object)
((slot-a :foreign-type :id :reader slot-a))
(defmethod initialize-instance :after ((self my-class) &key &allow-other-keys)
(defmethod (setf slot-a) ((new-value ns:ns-object) (self my-class))
(unless (eq (slot-a self) new-value)
(#/release (slot-a self ))
(setf (slot-value self 'slot-a) (#/retain new-value)))
(defmethod ccl::terminate ((self my-class))
(objc:defmethod (#/dealloc :void) ((self my-class))
(#/release (slot-a self))
This results in the reference count being balanced for all Lisp accesses. If the object is passed to Objective-C methods, they will presumably take care of doing proper retains and releases. The object's dealloc method is then only called when there are no more references from anywhere and it, in turn, is responsible for #/releasing Objective-C objects retained in its slots.
There is a hole in this process if the object is created by some Objective-C process (say by loading a NIB file that uses an instance of that class for something). In that case there is some Objective-C object that owns the object and it will, at some time in the future, release the object. If the GC determined that there were no Lisp references to the object, the ccl::terminate method would be called and it would prematurely release the object ... bad news for the Objective-C owner that might then use the reference.
So you can use the code above only for objects that are created in Lisp processes (i.e. you as the developer do a (make-instance ...) for it somewhere) and referenced from both Lisp and Objective-C objects. In effect, "Lisp" is the owner of the object.
For objects that are created by Objective-C methods and referenced only from Objective-C objects, don't use the ccl::terminate-when-unreachable and ccl::terminate methods; just rely on standard reference counting to take care of it. As before, objects retained in its slots should be released in the #/dealloc method.
For objects that are created by Objective-C methods (and therefore have an owner that will eventually release it) and referenced by both Objective-C and Lisp objects (any Lisp object can hold a reference to an Objective-C object; it doesn't have to be a :foreign-type slot) you have three choices:
You can choose to not use the ccl::terminate method and make sure that all Lisp references make proper #/retain and #/release calls just as they would for any other Objective-C object. For only a few Lisp references to the object this is probably preferred.
If there may be lots of Lisp references in lots of places that make doing #/retain and #release correctly problematic (e.g. in lists that are copied or sorted or whatever), then you have to think about whether any of the Lisp references might outlast the reference of the Objective-C owner object that created the referenced object in the first place. If not, then you can just treat all the Lisp references as weak references and forget about them.
But if you might want to use the reference from a Lisp object sometime after the creating owner has released it, then you'll want to make sure that it is retained for use by Lisp. To do that you might consider using the ccl::terminate-when-unreachable and ccl::terminate methods as described above, but with a twist. Do a single #/retain of the object at the time of the very first Lisp reference to the object. In effect, that says that Lisp is now an owner of the object. Then the corresponding #'release will be done when GC determines that there are no more Lisp references. One way to do this would be by adding something like (#/retain self) to the initialize-instance :after method, but you'll also want to make sure that you make an actual lisp reference to the object at that same point so that GC won't immediately invoke the ccl::terminate method which would prematurely #/release it. And of course that reference would have to be eliminated before GC would call the ccl::terminate method on it.
There may be other holes here that I'm not seeing ... Yes, memory management like this is a pain.
For completeness, we can also talk about a few cases where releases of Objective-C references are not necessary (if I am wrong about any of these, please correct):
1. Weak Objective-C references: objective-C slot that contains values known to be retained by some well-defined owner
Example: A NIB file defined in IB specifies references of various sorts between objects, some of which are Lisp instances (created when the nib is loaded) with objective-C slots that hold the reference. Since the nib owner is responsible for retaining and releasing all such objects it isn't necessary to do so for each slot. Of course if some other objective-C slot in one of these objects will later reference a dynamically created Objective-c object then previously discussed rules will apply.
2. Objective-C objects created using class convenience constructors. (I make significant use of these in my contrib stuff.) As per http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.pdf
"Many classes provide methods of the form +className... that you can use to obtain a new instance of
the class. Often referred to as “convenience constructors”, these methods create a new instance of the class,
initialize it, and return it for you to use. You do not own objects returned from convenience constructors, or
from other accessor methods."
On Sep 24, 2010, at 9:08 AM, Raffael Cavallaro wrote:
> I think it would be useful to know the best way to deal with the life cycle of cocoa objects. Possibly the various situations can be enumerated and memory management best practices outlined for each.
> Most importantly, please correct anything here that is wrong (i.e., likely to lead to a memory leak or outright crash), and please add any common situations that I've overlooked.
> 1. containing lisp class (i.e., not having ns:ns-object as its metaclass) with an objective-c slot.
> - call ccl::terminate-when-unreachable on the containing lisp instance in its intitialize-instance method
> - do necessary deallocation (#/release, etc.) of objective-c slots in a ccl:terminate method on the containing lisp class
> 2. containing Objetive-c class (with ns:ns-object as its metaclass) with a:
> 2a. lisp slot
> - assume this lisp slot is taken care of by the gc?
> 2b. objective-c slot
> option 1:
> - call ccl::terminate-when-unreachable on the containing objective-c instance in its intitialize-instance method
> - do necessary deallocation (#/release, etc.) of objective-c slots in a ccl:terminate method on the containing objective-c class
> option 2:
> - do necessary deallocation of objective-c slots in the containing objective-c class's #/dealloc method.
> option 3 ?:
> - for objective-c objects owned/managed by a custom window class, do necessary deallocation in the custom window's #/close method
> 3. Shared objective-c resources
> - put the shared resource into a special var and let it live for the duration of the program (it's memory will be reclaimed when the app quits)
> comments and corrections most welcome.
> warmest regards,
> Raffael Cavallaro
> raffaelcavallaro at me.com
> Openmcl-devel mailing list
> Openmcl-devel at clozure.com
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the Openmcl-devel