views:

315

answers:

1

I have an app (using retain/release, not GC) that maintains an NSArray instance variable, which is exposed as a property like so:

@interface MyObject : NSObject
{
    NSArray* myArray;
}
@property (copy) NSArray* myArray;
@end

I want to access the contents of this array from a secondary thread, which is detached using -performSelectorInBackground:withObject:. It is possible and indeed likely that the array will change during the execution of the secondary thread.

In the secondary thread I want to do something like this:

if([self.myArray containsObject:foo])
{
    //do stuff
}

From reading the threading documentation, it seems I should be able use the @synchronized directive in the accessors like so:

@implementation MyObject
- (NSArray *)myArray
{
    NSArray *result;
    @synchronized(self)
    {
        result = [myArray retain];
    }
    return [result autorelease];
}

- (void)setMyArray:(NSArray *)aMyArray
{
    @synchronized(self)
    {
        [myArray release];
        myArray = [aMyArray copy];
    }
}
@end

Is this all I need to do to ensure thread safety, or is it more complex?

Update: I've subsequently found a great article on Apple's site that addresses this issue in depth: http://developer.apple.com/mac/library/technotes/tn2002/tn2059.html

+1  A: 

Your code above protects you from setting the array concurrently, or getting the array while another is setting it. Since it is a non-mutable array, this protects the array itself just fine.

However, if by "the array will change" you mean you'll be editing the items inside the array, you could still have some problems. For example, if the array was filled with NSMutableStrings, and you had a thread that ran:

NSMutableString *foo = [myObject.myArray objectAtIndex:0];
[foo appendString:@"foo"];

and another that ran

NSMutableString *bar = [myObject.myArray objectAtIndex:0];
[bar appendString:@"bar"];

The access to the array would be safe (one thread would have to wait for the other to access it), however, access to the foo/bar pointer (which is the same) would not be, since both calls to 'appendString' are outside of the @synchronized block.

If this is how your array will change, you'll need to synchronize these points of access as well. Either with more @synchronized blocks, or other types of locks. See Using Locks

bobDevil
Thanks, that's exactly what I was looking for. In my case the items in the array are immutable `NSString` objects so I won't need to worry about any of the items in the array being mutated.
Rob Keniger
Instead of declaring the property `@property (copy) NSArray* myArray;` and writing the `@synchronized` blocks yourself, why not just use `@property (atomic, copy) NSArray* myArray;`?
CajunLuke
CajunLuke:There isn't an atomic keyword because properties are atomic by default.
Preston
The basic question stands, though – why use trivial custom accessors? @synthesize myArray; will do the same thing, only more efficiently.
Ahruman
Ahruman hit the nail on the head. The property is declared to support atomic access.And bobDevil is right, too: that's not enough to guarantee thread safety.If you ever find yourself wondering, "Is this enough to guarantee thread safety?" then the answer is always no. Only when you have proven that your code is threadsafe is it enough, and then only until you write more code. Thread safety is a very, very fragile property. (And don't forget about deadlock free, starvation free, liveness, priority inversion… If you don't absolutely need threads, then don't go there.)
Jeremy W. Sherman