How do I safely access the contents of an NSArray property from a secondary thread?
-
19-09-2019 - |
Question
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
Solution
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