mulle_objc: object layout, retain counting, finalize
Continued from mulle-objc: hashes for classes, selectors and protocols
mulle_objc uses a uniform memory layout for all instances.
The isa
pointer is no longer part of the self->
accessible instance variable area. Therefore isa
(currently) does not exist in mulle-objc.
You can access the isa pointer with
mulle_objc_object_get_class
and it’s not unlikely, that I will teach the compiler to use that when accessingself->isa
some time in the future.
This means
- Instances can be just casted to C struct pointers, if one desires
- C++ objects can be wrapped into ObjC objects, if one desires
- Tagged pointers for objects are impossible
- The first instance variable alignment is, depending on the mallocer, not made more worse than 64 bit alignment on 32 bit or 128 bit on 64 bit.
The retainCount is now always part of the memory layout. It gains a secondary meaning, when the retainCount is negative (though -retainCount
will never show negative values). Such an object is considered to be finalized.
finalize makes a comeback
Problems with the legacy retainCount/dealloc architecture
- due to the nature of NSAutoreleasePools it’s very hard to control
when exactly and in which order an objects’ resources are released. With
-performFinalize
(see below) it can be done explicitly. - the object that is executing
-dealloc
may call a method on itself or call another object with itself as a parameter, which can lead to itself being retained. This can lead to misery and should be avoided. Move complex code to-finalize
.
The rules are pretty simple, when the retainCount is decremented to zero, an object gets the -finalize
message first (if -finalize
has not been called before) before -dealloc
. If the retainCount remains unchanged throughout -finalize
, then -dealloc
is called. It is guaranteed that -finalize
is only called once.
If one wants to finalize “manually”, one should use -performFinalize
as this properly massages the retainCount. Directly calling -finalize
from outside the runtime is an error.
In most ObjC runtimes -dealloc
is used to free resources and cut
links to other objects. Now -dealloc
will ideally at most contain -release
calls and [super dealloc]
. Anything else can be done in -finalize
. The idea is, that a finalized object is still
useable in the object hierarchy, but not active anymore. A good example, where this is useful, is a window controller, where the window close button has been clicked. I, t may still redraw, but it doesn’t react to any event actions any more.
Points of interest
-finalize
is single-threaded by design, just like-init
and-dealloc
have always been (which may be useful to know). But when you-performFinalize
it can only be guaranteed that no other thread is executing-finalize
, not necessarily that no other thread is accessing the object. (More on that later)
That’s just the way it is, some things change
As I removed the flexibility of the way objects are structured and embedded the retainCount by decree, there is no real point in overriding -retain
/-release
/-retainCount
anymore (except possibly for debugging). Because of this -retain
and -release
are now compiled into nicely inlinable atomic functions, with no method or function call overhead.
Continued to mulle-objc: inheriting from protocols
Post a comment
All comments are held for moderation; basic HTML formatting accepted.