Please note: this article is part of the older "Objective-C era" on Cocoa with Love. I don't keep these articles up-to-date; please be wary of broken code or potentially out-of-date information. 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 (
char
,short
,int
,float
,long
,double
, etc). - 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:
@property SomeAtomicType somePropertyVariable;
the custom getter and setter simply look like this:
- (SomeAtomicType)somePropertyVariable
{
return somePropertyVariable;
}
- (void)setSomePropertyVariable:(SomeAtomicType)aValue
{
somePropertyVariable = aValue;
}
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
struct
s 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:
@property NSString (copy) someString;
A hasty implementation of the setter might be:
- (void)setSomeString:(NSString *)aString
{
[someString release];
someString = [aString copy];
}
This implementation actually contains two bugs:
- This method is not atomic.
ThesomeString
object changes twice: once onrelease
and again when it is assigned the copied object's address. This method is not atomic and therefore violates the declaration (which omits thenonatomic
keyword and therefore requires atomicity). - The assignment contains a potential memory deallocation bug.
IfsomeString
is ever assigned its own value, it willrelease
it beforecopy
ing it, causing potential use of arelease
d 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:
- (void)setSomeString:(NSString *)aString
{
if (someString != aString)
{
[someString release];
someString = [aString copy];
}
}
This only fixes the memory issue, it doesn't fix the atomicity issue. To handle that, the only simple solution is to used a @synchronized
section:
- (void)setSomeString:(NSString *)aString
{
@synchronized(self)
{
if (someString != aString)
{
[someString release];
someString = [aString copy];
}
}
}
This approach will also work for retain
properties as well (simply replace the copy
method with a retain
.
To maintain atomicity, you also need a retain/autorelease
pattern and lock on any getter methods too:
- (NSString *)someString
{
@synchronized(self)
{
id result = [someString retain];
}
return [result autorelease];
}
The @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).
For struct
and other compound data types, we don't need to retain
or copy
, so only the @synchronized
section is required:
- (NSRect)someRect
{
@synchronized(self)
{
return someRect;
}
}
- (void)setSomeRect:(NSRect)aRect
{
@synchronized(self)
{
someRect = aRect;
}
}
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.
- The
@synchronized
section onself
is 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:
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic);
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic,
BOOL shouldCopy);
void objc_copyStruct(void *dest, const void *src, ptrdiff_t size, BOOL atomic,
BOOL hasStrong);
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:
#define AtomicRetainedSetToFrom(dest, source) \
objc_setProperty(self, _cmd, (ptrdiff_t)(&dest) - (ptrdiff_t)(self), source, YES, NO)
#define AtomicCopiedSetToFrom(dest, source) \
objc_setProperty(self, _cmd, (ptrdiff_t)(&dest) - (ptrdiff_t)(self), source, YES, YES)
#define AtomicAutoreleasedGet(source) \
objc_getProperty(self, _cmd, (ptrdiff_t)(&source) - (ptrdiff_t)(self), YES)
#define AtomicStructToFrom(dest, source) \
objc_copyStruct(&dest, &source, sizeof(__typeof__(source)), YES, NO)
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:
- (NSString *)someString
{
return AtomicAutoreleasedGet(someString);
}
- (void)setSomeString:(NSString *)aString
{
AtomicCopiedSetToFrom(someString, aString);
}
and the someRect accessor methods shown above would become:
- (NSRect)someRect
{
NSRect result;
AtomicStructToFrom(result, someRect);
return result;
}
- (void)setSomeRect:(NSRect)aRect
{
AtomicStructToFrom(someRect, aRect);
}
Conclusion
Most of the accessor methods I've shown here are atomic but in reality, most Objective-C object accessors are declared nonatomic
.
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:
#define NonatomicRetainedSetToFrom(a, b) do{if(a!=b){[a release];a=[b retain];}}while(0)
#define NonatomicCopySetToFrom(a, b) do{if(a!=b){[a release];a=[b copy];}}while(0)
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
volatile
qualifiers or a@synchronized
section around the assignment to flush caches.
How blocks are implemented (and the consequences)