Reader beware: this post is part of the older "Objective-C era" on Cocoa with Love. I don't keep these articles up-to-date so the code may be broken or superceded by newer APIs. There's some good information but there's also some opinions I no longer endorse – keep a skeptical mind. Read "A new era for Cocoa with Love" for more.
Objective-2.0 property methods are a nice convenience but if you need to override a property implementation — particularly an atomic, retained or copied object setter property — there are some potential bugs you can create if you don't follow the rules carefully. I'll show you the pitfalls and the correct way to implement a property accessor. I'll also show a way to directly invoke hidden runtime functions to let Objective-C perform atomic getting and setting safely for you.
Custom getter and setter methods for implicitly atomic types
For implicitly atomic types or for types where memory management doesn't apply, custom getter and setter methods in Objective-C are easy. These "easy" situations include:
- Basic value types (
- Objective-C objects in a garbage collected environment
- Assigned (non-retained) pointers
For these types, it is pretty hard to get a custom getter or setter method wrong. For the following property declaration:
the custom getter and setter simply look like this:
Common mistakes in accessor methods for non-atomic types
Non-atomic types require greater care. These types include:
- Objective-C objects in a manually managed memory environment
structs and other compound types
Given how simple custom getter and setter methods are for atomic types, it is easy to be complacent about implementing methods for these types. However, following the wrong approach can lead to memory crash bugs and lack of proper thread safety.
To illustrate how simple it can be to introduce bugs while implementing a custom setter method, consider the following declared property:
A hasty implementation of the setter might be:
This implementation actually contains two bugs:
- This method is not atomic.
someStringobject changes twice: once on
releaseand again when it is assigned the copied object's address. This method is not atomic and therefore violates the declaration (which omits the
nonatomickeyword and therefore requires atomicity).
- The assignment contains a potential memory deallocation bug.
someStringis ever assigned its own value, it will
copying it, causing potential use of a
released variable. The code:
self.someString = someString;is an example of this potential issue.
Don't feel too bad if you've ever made these mistakes. I spent some time looking at clang's synthesized method implementations when I was researching this post and I noticed that they've forgotten to handle struct accessor methods in an atomic manner when required.
Safe implementations of custom accessor methods for non-atomic types
To address this second issue, Apple's Declared Properties documentation suggests that your setter methods should look like this:
This only fixes the memory issue, it doesn't fix the atomicity issue. To handle that, the only simple solution is to used a
This approach will also work for
retain properties as well (simply replace the
copy method with a
To maintain atomicity, you also need a
retain/autorelease pattern and lock on any getter methods too:
@synchronized section is only required around the
retain since that will prevent a setter releasing the value before we can return it (the
autorelease is then safely done outside the section).
struct and other compound data types, we don't need to
copy, so only the
@synchronized section is required:
A faster, shorter way to implement custom accessors
There are two negative points to the custom accessor methods listed above:
- They need to be coded exactly to avoid bugs.
selfis coarse-grained and slow.
There is another way to implement these methods that doesn't require as much careful coding and uses much more efficient locking: use the same functions that the
synthesized methods use.
The following functions are implemented in the Objective-C runtime:
While these functions are implemented in the runtime, they are not declared, so if you want to use them, you must declare them yourself (the compiler will then find their definitions when you compile).
These methods are much faster than using a
@synchronized section on the whole object because (as shown in their Apple opensource implementation) they use a finely grained, instance variable only spin lock for concurrent access (although the copy struct function uses two locks following an interface design mixup).
When you declare these functions, you can also declare the following convenience macros:
I like to include the "To/From" words so I can remember the ordering of the source and destination parameters. You can remove them if they bother you.
With these macros, the
someString "copy" getter and setter methods above would become:
and the someRect accessor methods shown above would become:
Most of the accessor methods I've shown here are atomic but in reality, most Objective-C object accessors are declared
Even if your properties are declared
nonatomic, the memory management rules still apply. These rules are important to follow since they can lead to some very obscure and hard to track down memory bugs.
The macros I've provided are all for atomic properties. For non-atomic properties the boilerplate assignment code is probably simple enough to remember. If not, you could also use a macro:
Update: following comments below, I realize I omitted to qualify the situations in which these accessors are thread-safe. Specifically:
- These setter methods are only thread-safe if the parameters passed to them are immutable. For mutable parameters, you may need to ensure thread safety between mutations on the parameter and the assignment of the property.
- Atomic accessors only provide thread safety to an instance variable if they are the sole way you access the instance variable. If non-property access is required, you must ensure shared thread safety between property accessor methods and the non-property access.
- Atomic assignment for the "implicitly atomic" types I listed does not mean that all CPUs/cores see the same thing (since each CPU/core could have its own cache of the value) — it only ensures that value is wholly set without possibility of interruption. If you require all CPUs/core to be synchronized and see the same value at a given moment, then even the "implicitly atomic" types may require
volatilequalifiers or a
@synchronizedsection around the assignment to flush caches.
How blocks are implemented (and the consequences)