Thread-safe is about making mutable shared state either immutable, or unshared. In this case, synchronize and serial queues are ways to temporarily unshare (prevent concurrent access), and both are valid.
However, consider the case where you have disjoint sets of related info inside the same object. For example, a bill with 1) parts of an address (city, street, etc), and 2) price, taxes, discount. Both need to be protected to avoid inconsistent state (object A sets a new street, while object B reads the old city and the new street), but both are unrelated. In this case, you should use the lower level of granularity to avoid blocks between unrelated code.
So a rule would be: don't use synchronize on unrelated sets of variables inside the same object, because it would cause unneeded blocks between them. You can use a queue + synchronize, or a queue per set, but not synchronized on both sets.
The key is that synchronize refers to the one and only intrinsic lock of the object, and that token can only be held by once. It accomplishes the same goal as if you routed all related code through one queue, except that you can have multiple queues (and thus, lower granularity), but only one intrinsic lock.
Back to your example. Assuming that the object is documented as “state X is protected by synchronize”, the use of synchronize inside the queue is useful to block related methods that could access that same state. So maybe queue and synchronized are protecting different things, or the serial queue is there to perform a different task.
Another reason to prefer a queue is to write a more sophisticated pattern like a read-write lock. Example:
NSMutableDictionary *_dic = [NSMutableDictionary new];
dispatch_queue_t _queue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
- (id) objectForKey:(id)key
{
__block obj;
dispatch_sync(_queue, ^{
obj = [_dic objectForKey: key];
});
return obj;
}
- (void) setObject:(id)obj forKey:(id)key
{
// exclusive access while writing
dispatch_barrier_async(_queue, ^{
[_dic setObject:obj forKey:key];
});
}