4

I have an NSMutableArray in a "sharedStore"-pattern singleton.

Publicly, it's accessible only through methods that cast it as an NSArray. Within the class, it's

@property (nonatomic, copy) NSMutableArray *myItems;

This array never gets manipulated outsdie the singleton but ViewControllers send the singleton messages to manipulate this controller. Some of these messages empty the array, some re-populate it, etc.

Having ended up in a situation where the array was empty in one method call and not yet empty in the next, I've started implementing some concurrency behaviour.

Here's what I'm doing so far:

In the .m file of the singleton, I have a

@property (nonatomic, strong) dispatch_queue_t arrayAccessQueue;

In my singleton's initializer it gets created as a serial queue. And then, every method that has anything to do with mutating this array does so from within a dispatch_sync call, for example:

dispatch_sync(self.arrayAccessQueue, ^{
    [_myItems removeAllObjects];
});

This has made things better and has made my app behave more smoothly. However, I have no way of quantifying that beyond it having fixed that one odd behaviour described above. I also kind of feel like I'm in the dark as to any problems that may be lurking beneath the surface.

This pattern makes sense to me, but should I be using something else, like @synchronize or NSLock or NSOperationQueue? Will this come back to bite me?

4
  • BTW - did you properly implement an explicit setter method for your myItems property? If not, you will have problems due to the copy attribute on the mutable property. Commented May 29, 2015 at 14:58
  • Hmm. I did not know that. You mean _myItems = [myItems copy]? I thought the compiler did that automatically now and that it didn't need to be done explicitly anymore? Am I wrong? Commented May 29, 2015 at 16:51
  • 1
    The problem is that [myItems copy] returns an NSArray, not an NSMutableArray, even when called on a mutable array. You need to override the setMyItems: method and call mutableCopy. Commented May 29, 2015 at 16:54
  • Damn, good point. It hadn’t yet caused any problems because I was referring to it as _myItems in my code. (Yes, bad form...) Commented May 29, 2015 at 16:58

3 Answers 3

4

Using dispatch_sync is fine as long as you wrap all array reads and writes and you ensure it is a serial queue.

But you could improve things by allowing concurrent reads. To do this, use dispatch_sync around all array reads and use dispatch_barrier_sync around all array writes. And setup the queue to be concurrent.

Do this ensures only a single write can happen at a time, reads will be block until the write is done, and a write will wait until all current reads are done.

Sign up to request clarification or add additional context in comments.

10 Comments

Thanks. I would also use the barrier for removing objects, correct?
Yes since removing is considered a "write". Anything that changes the contents of the array is a "write" and need to use the barrier sync.
Technically, NSMutableArray is not thread safe for readonly operations, either. Unless the documentation explicitly states otherwise, you cannot assume thread safety. (I'm pretty sure it is actually implemented to be thread safe during read operations, though.) This probably needs to be clarified in the documentation.
@bbum I can't imagine any implementation of NSMutableArray/NSArray that would render concurrent reads as unsafe.
@rmaddy I can and have seen such implementations, but they are rare. Mutable collections often transition from lots-of-reads-and-writes to almost-exclusively-readonly. When that transition is detected or hinted, the array may internally optimize itself for retrieval speed vs. insertion speed. I don't think that is in play here, but -- as with all things threading -- assume thread unsafe until claimed (and tested) otherwise!
|
1

Using a GCD queue concurrent and providing sort of accessor to your array you can synchronize reading and writing by using dispatch_sync while reading and dispatch_barrier_async while writing.

- (id)methodToRead {
  id __block obj = nil;
  dispatch_sync(syncQueue, ^{
     obj = <#read_Something#>;
  });
  return obj;
}

- (void) methodsForWriting:(id)obj {
   dispatch_barrier_async(syncQueue, ^{
    // write passing obj to something
  });
}

This will guarantee that during writing everything is locked from reading.

2 Comments

Shouldn't it be dispatch_barrier_sync for writing?
Depends, _async means that the method returns before the block is executed. But block execution sequence should be guaranteed by the sync queue itself. Of course it sounds more appropriate .
0

Using GCD is the right choice. The only "gotcha" is that you need to do ALL operations on that queue: add, remove, insert, etc.

I will also mention you need to ensure that you do not use a concurrent queue. You should be using a serial queue, which is the default anyways.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.