Nat! bio photo


Senior Mull

Twitter Github Twitch

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.

Object Layout

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 accessing self->isa some time in the future.

This means

  1. Instances can be just casted to C struct pointers, if one desires
  2. C++ objects can be wrapped into ObjC objects, if one desires
  3. Tagged pointers for objects are impossible
  4. 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.

E-mail: (not published)