Next article: Friday Q&A 2010-12-17: Custom Object Allocators in Objective-C
Previous article: Friday Q&A 2010-11-19: Creating Classes at Runtime for Fun and Profit
Tags: accessors fridayqna memory objectivec threading
It's once again time for a brand new edition of Friday Q&A. This week, I'm going to talk about accessors, and how to properly deal with memory management and thread safety when creating them, a topic suggested by Daniel Jalkut.
More Complicated Than You Might Think
Cocoa's reference counting memory management usually works pretty well, and for the most part accomplishes its goal of making memory management decisions a local, rather than global, affair.
There's a big exception. Spot the bug in this code:
NSMutableDictionary *dict = ...;
id obj = [dict objectForKey: @"foo"];
[dict removeObjectForKey: @"foo"];
[obj something];
obj
from dict
, you've potentially destroyed the object, which then causes your program to crash on the last line.
Of course, this bug isn't always this easy to find. The removal may be buried several method calls deep, and the object may sometimes be retained elsewhere, hiding the bug and making your program crash only inconsistently.
The same problem can happen with a regular object and accessors:
id obj = [otherObj foo];
[otherObj setFoo: newFoo];
[obj something]; // crash
-foo
and -setFoo:
are implemented.
Basic Accessors
The most basic accessors look something like this:
- (void)setFoo: (id)newFoo
{
[newFoo retain];
[_foo release];
_foo = newFoo;
}
- (id)foo
{
return _foo;
}
This does not mean that these basic accessors are wrong. It does mean that you need to be more careful when writing code that uses them:
id obj = [[otherObj foo] retain];
[otherObj setFoo: newFoo];
[obj something]; // safe
[obj release];
Autoreleasing Accessors
Rather than make callers retain and release temporary objects, another approach is to modify the accessors to use autorelease
. You can do this in the setter:
- (void)setFoo: (id)newFoo
{
[_foo autorelease];
_foo = [newFoo retain];
}
- (id)foo
{
return [[_foo retain] autorelease];
}
retain
/autorelease
combo keeps the object alive even after the setter releases it.
This solves the problem, but there are a couple of downsides. The obvious one is that it's less efficient. Using autorelease
is a bit slower than release
, and it keeps the target object alive longer, which could lead to more memory usage. This isn't usually very important, but it's something to keep in mind.
More importantly, pervasive use of autorelease
can make it harder to track down memory management errors. If you over-release
an object, you want to crash as soon as possible. Using autorelease
can often cause the crash to be in NSAutoreleasePool
code, making it considerably harder to track down. Using zombies and Instruments will help a lot, but it can still make your job harder.
Thread Safe Accessors
When writing thread safe accessors, the autorelease
getter becomes mandatory. Another thread could call the setter after it returns, and the retain
/autorelease
dance is the only way to ensure that this does not destroy the previous object in the middle of being used.
Thread safe accessors, like any access to shared data, need to use a lock. You can use @synchronized(self)
, an instance of NSLock
, or whatever other locking mechanism you prefer.
(Lockless access to shared data is, of course, possible, but far too tricky and difficult to cover here. Better to skip it and use locks.)
By locking all shared access, and by using the autorelease
version of the getter, you end up with thread safe accessors:
- (void)setFoo: (id)newFoo
{
[newFoo retain];
@synchronized(self)
{
[_foo release];
_foo = newFoo;
}
}
- (id)foo
{
id returnFoo;
@synchronized(self)
{
returnFoo = [_foo retain];
}
return [returnFoo autorelease];
}
Do You Need Thread Safe Accessors?
In almost every case, the answer to this question is "no". Accessors are usually not the right place to worry about thread safety.
The problem is that thread safety isn't a composable attribute. If you take a bunch of thread safe components and bundle them together, the result may not be thread safe.
For a simple example, imagine a Person
class with properties for the first and last name of the person it represents. One thread does this:
[person setFirstName: @"John"];
[person setLastName: @"Doe"];
NSString *f = [person firstName];
NSString *l = [person lastName];
NSString *fullName = [NSString stringWithFormat: @"%@ %@", f, l];
fullName
of "John Smith", which doesn't correspond to either person.
In order to make this code safe, thread safety needs to be applied at a higher level. For example, you might have a PersonDatabase
object which could be locked and unlocked for any manipulations. You might decree that all Person
access go through a single serial Grand Central Dispatch queue. You might just make all Person
access happen on the main thread.
No matter which solution you pick, once you've picked it, the thread safe accessors are no longer necessary. The larger-scale thread safety takes care of problems, and all the thread safe accessors do is make your code unnecessarily slow and complex.
There are cases where a thread safe accessor is useful. You may have a single property that's accessed from many threads and which won't experience problems being inconsistent with other properties, in which case you'd want a thread safe accessor. However, 99% of the time there is no point in making them.
Properties and nonatomic
Given the above, it's extremely puzzling that Apple has made @property
declarations default to atomic
. Most of the time it's pointless, and it can give the mistaken impression that the programmer doesn't have to worry about thread safety anymore, because the @property
handles it all.
The @property
and @synthesize
constructs can be a good way to generate accessors without writing code, just be aware that the default atomic
behavior is not all that useful, and doesn't mean you can forget about thread safety.
Garbage Collection
If you're using garbage collection, this whole question becomes vastly simpler. Here's what a correct, thread safe accessor pair looks like in garbage collected code:
- (void)setFoo: (id)newFoo
{
_foo = newFoo;
}
- (id)foo
{
return _foo;
}
Conclusion
Writing accessors is easy, but there are some subtleties. The vast majority of the time, a plain retain
/release
setter and a return _ivar
getter is all you need. However, depending on your situation and your individual taste, you may want to put retain
/autorelease
into the getter in order to simplify the code that calls it.
When it comes to thread safety, accessors are usually the wrong place to worry about it. While there are legitimate cases where it's useful, most of the time you should back up and take on the problem of thread safety at a higher level of your code. And don't let the atomic
@property
keyword fool you: it doesn't do anything special in this regard.
If you're coding exclusively for garbage collection, you can pretty much forget about this whole business and write much simpler code.
That's it for this edition of Friday Q&A. As usual, another one will be posted in two weeks. Until then, you can pass the time by sending me suggestions for topics. If you have an topic that you would like to see covered here, please send it in.
Comments:
// thread 1
[_foo release];
// thread 2
id foo = [obj foo]
// thread 1
_foo = [newFoo retain];
Thread 2 ends up with a reference to a released, potentially destroyed object.
[_foo retain]
is not an atomic operation. It's split into two steps: retrieving the value of _foo
, and then sending retain to it. If the setter were to run in between those two steps, the retain
would be sent to a dangling pointer and can crash.Comments RSS feed for this page
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.