Memory and thread-safe custom property methods
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.
the custom getter and setter simply look like this:
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 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
To maintain atomicity, you also need a
The
For
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
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
and the someRect accessor methods shown above would become:
Even if your properties are declared
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:
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
@property SomeAtomicType somePropertyVariable; |
- (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
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; |
- ( void )setSomeString:( NSString *)aString { [someString release ]; someString = [aString copy ]; } |
- 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.
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 ]; } } |
@synchronized
section:- ( void )setSomeString:( NSString *)aString { @synchronized ( self ) { if (someString != aString) { [someString release ]; someString = [aString copy ]; } } } |
retain
properties as well (simply replace the copy
method with aretain
.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 ]; } |
@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.
synthesized
methods use.The following functions are implemented in the Objective-C runtime:
id <strong>objc_getProperty</strong>( id self , SEL _cmd , ptrdiff_t offset, BOOL atomic); void <strong>objc_setProperty</strong>( id self , SEL _cmd , ptrdiff_t offset, id newValue, BOOL atomic, BOOL shouldCopy); void <strong>objc_copyStruct</strong>( void *dest, const void *src, ptrdiff_t size, BOOL atomic, BOOL hasStrong); |
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 <strong>AtomicRetainedSetToFrom</strong>(dest, source) \ objc_setProperty( self , _cmd , (ptrdiff_t)(&dest) - (ptrdiff_t)( self ), source, YES , NO ) #define <strong>AtomicCopiedSetToFrom</strong>(dest, source) \ objc_setProperty( self , _cmd , (ptrdiff_t)(&dest) - (ptrdiff_t)( self ), source, YES , YES ) #define <strong>AtomicAutoreleasedGet</strong>(source) \ objc_getProperty( self , _cmd , (ptrdiff_t)(&source) - (ptrdiff_t)( self ), YES ) #define <strong>AtomicStructToFrom</strong>(dest, source) \ objc_copyStruct(&dest, &source, sizeof(__typeof__(source)), YES , NO ) |
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); } |
- (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 declarednonatomic
.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 <strong>NonatomicRetainedSetToFrom</strong>(a, b) do{if(a!=b){[a release];a=[b retain];}}while(0) #define <strong>NonatomicCopySetToFrom</strong>(a, b) do{if(a!=b){[a release];a=[b copy];}}while(0) |
- 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.
No comments:
Post a Comment
Please comment here...