Atomic And Nonatomic Properties
Two weeks ago we looked at
the
@property
directive and last week we saw how to use
the
@synthesize
directive to tell the compiler to generate
getters and setters for properties. We've covered the most commonly
used attributes of the @property
directive:
readonly
/readwrite
and
assign
/retain
/copy
. Today we'll
look at the nonatomic
attribute and talk about what it's
for and why you should (or should not) use it.
Threads and data
By default, your app's code executes on the main thread of your app's
process, along with most Cocoa Touch framework code. Any particular
method or function runs uninterrupted from start to finish and as long
as that method or function leaves all the data it touches in a good
state when it returns, your program runs correctly.
When you have multiple threads in your application, things aren't so
easy. One key challenge when using multiple threads is to make sure
you only read data when it's in a consistent state. This is similar in
concept to using a transaction in a SQL database.
Suppose you have a custom UI object that's defined like this:
@interface MyWidget {
CGPoint center;
// ...
}
@property CGPoint center;
// ...
@end
@implementation
@synthesize center;
// ...
@end
If you treat instances of this class as read only when you share them
between threads, you're safe. The trouble appears when one or both
threads start to make changes to the object. If we were to write the
getter and setter for center
, it would look like this:
// example assign-type getter and setter
- (CGPoint) center {
return center;
}
- (void)setCenter:(CGPoint)theCenter {
center = theCenter;
}
This looks simple enough, but the compiler is helping us out here. The
center
instance variable is a struct
that's
defined like this:
// struct CGPoint
struct CGPoint {
CGFloat x;
CGFloat y;
};
The setCenter:
method is actually doing something like
this:
- (void)setCenter:(CGPoint)theCenter {
center.x = theCenter.x;
center.y = theCenter.y;
}
Let's look at what happens when one thread calls the setter and a
second thread calls the getter. In the simple case, the setter and
getter calls don't overlap:
// given MyWidget instance myWidget:
// thread 1 calls setter:
[myWidget setCenter:CGPointMake(1.0f, 2.0f)];
// setCenter method executes:
- (void)setCenter:(CGPoint)theCenter {
center.x = theCenter.x; // 1.0f
center.y = theCenter.y; // 2.0f
}
// center is now {1.0f, 2.0f}
// ... thread 1 preempted by thread 2 ...
// thread 2 calls getter:
CGPoint point = [myWidget center];
// center method executes:
- (CGPoint) center {
return center; // 1.0f, 2.0f
}
// point is now {1.0f, 2.0f}
In this case, we get the answer we expect. Now suppose we do this
again, only thread 1 gets preempted by thread 2 in the middle of the
setCenter
method:
// myWidget.center is {1.0f, 2.0f}
// thread 1 calls setter:
[myWidget setCenter:CGPointMake(3.0f, 5.0f)];
// setCenter method executes:
- (void)setCenter:(CGPoint)theCenter {
center.x = theCenter.x; // 3.0f
// ... thread 1 preempted by thread 2 ...
// thread 2 calls getter:
CGPoint point = [myWidget center];
// center method executes:
- (CGPoint) center {
return center; // 3.0f, 2.0f
}
// point is now {3.0f, 2.0f}
center.y = theCenter.y; // 5.0f
}
// myWidget.center is now {3.0f, 5.0f}
// but thread 2 read {3.0f, 2.0f}
Now thread 2 is working off of a corrupt value and things are likely to
go haywire. To solve this problem, we need to prevent all threads from
reading center
until the setCenter:
method is
finished. Because this is a common problem in multithreaded code,
Objective-C has a special directive to accomplish this:
@synchronized
. We can rewrite our getter and setter for
the center
property like this:
// adding @synchronized to getter and setter
- (CGPoint) center {
CGPoint theCenter;
@synchronized(self) {
theCenter = center;
}
return theCenter;
}
- (void)setCenter:(CGPoint)theCenter {
@synchronized(self) {
center = theCenter;
}
}
Now when we read and write center
from two threads,
@synchronized
causes other threads to pause whenever one
thread is inside either of the @synchronized
blocks:
// myWidget.center is {1.0f, 2.0f}
// thread 1 calls setter:
[myWidget setCenter:CGPointMake(3.0f, 5.0f)];
// setCenter method executes:
- (void)setCenter:(CGPoint)theCenter {
@synchronized(self) {
center.x = theCenter.x; // 3.0f
// ... thread 1 preempted by thread 2 ...
// thread 2 calls getter:
CGPoint point = [myWidget center];
// center method executes:
- (CGPoint) center {
CGPoint theCenter;
@synchronized(self) {
// thread 1 is already synchronized on
// self so thread 2 pauses here
// ... thread 2 yields and thread 1 runs again ...
// still inside @synchronized on thread 1
center.y = theCenter.y; // 5.0f
}
}
// ... thread 1 preempted by thread 2 again ...
@synchronized(self) {
// now thread 2 resumes
theCenter = center; // 3.0f, 5.0f
}
return theCenter; // 3.0f, 5.0f
}
// point is now {3.0f, 5.0f}
I'm glossing over many of the details of @synchronized
here. If you're writing multithreaded code, you should read the
Threading
Programming Guide. The key concept here is that by default, the
@synthesize
directive generates this type of
synchronization for you.
Atomic or nonatomic
Behind the scenes, the @synchronized
directive uses a lock
to prevent two threads from accessing a @synchronized
block simultaneously. Although acquiring and releasing the lock is
very quick, it's not free. Occasionally you have a property that is so
frequently accessed that all this locking and unlocking adds up to a
noticeable penalty. In these rare cases, you can declare the property
to be nonatomic
:
@interface MyWidget {
CGPoint center;
// ...
}
@property (nonatomic) CGPoint center;
// ...
@end
The compiler omits the synchronization code when generating
nonatomic
getters and setters. Note that there isn't a
corresponding atomic
attribute for @property
;
generated getters and setters are synchronized by default.
Don't prematurely optimize
Acquiring a lock is very fast in the common case where no other thread
is holding it. According to Apple's docs, it takes about 0.0000002
seconds (that's 0.2 microseconds) on a modern Mac. Even though the
iPhone is much slower, you need to be acquiring locks hundreds of
thousands of times before you should consider synchronization overhead
as anything significant. For the vast majority of code, you should
simply not even worry about nonatomic
.
Also, keep in mind that the attributes you set on your
@property
declarations only apply when you use
@synthesize
to have the compiler generate the getter and
setter methods. If you write the getter or setter yourself, the
attributes are ignored.
Next
week we'll look a little more at synchronization and show you how
to write a thread safe getter when returning an Objective-C object.