Objective-C
Singleton
@synchronized
Thread Safety
iOS Development

What does synchronized do as a singleton method in objective C?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Objective-C singleton code, @synchronized provides mutual exclusion so only one thread executes a protected block at a time. It can make lazy singleton initialization thread-safe, but it is not the preferred modern pattern for one-time instance creation. To use it correctly, you need to understand lock scope, lock object choice, and alternatives such as dispatch_once.

What @synchronized Actually Does

@synchronized(lockObject) acquires a runtime lock associated with lockObject, executes the block, then releases the lock even if an exception occurs.

objective-c
1+ (instancetype)sharedInstance {
2    static MyManager *instance = nil;
3
4    @synchronized(self) {
5        if (instance == nil) {
6            instance = [[MyManager alloc] init];
7        }
8    }
9
10    return instance;
11}

In this pattern, locking prevents two threads from creating two instances simultaneously.

Why dispatch_once Is Usually Better

For singleton initialization, dispatch_once is designed specifically for exactly-once execution and typically has lower post-init overhead.

objective-c
1+ (instancetype)sharedInstance {
2    static MyManager *instance = nil;
3    static dispatch_once_t onceToken;
4
5    dispatch_once(&onceToken, ^{
6        instance = [[MyManager alloc] init];
7    });
8
9    return instance;
10}

This is the common recommendation for singleton creation in modern Objective-C code.

Lock Scope Matters

Even when @synchronized is correct, lock scope should stay minimal. Long operations inside the lock increase contention and startup latency.

Avoid this pattern:

  • Lock acquired.
  • Disk I/O and heavy parsing inside lock.
  • Network initialization inside lock.
  • Lock released.

Prefer creating the instance quickly under lock and moving expensive setup elsewhere.

Choosing the Lock Object Consistently

In class methods, @synchronized(self) locks on class object. That is valid, but consistency is critical. If one code path uses class object and another uses instance object for related state, synchronization guarantees break.

For non-singleton critical sections, a private lock object often improves clarity.

objective-c
1@interface TokenStore ()
2@property (nonatomic, strong) NSObject *lock;
3@end
4
5- (instancetype)init {
6    self = [super init];
7    if (self) {
8        _lock = [NSObject new];
9    }
10    return self;
11}

Then use @synchronized(self.lock) for that specific state.

Migration from @synchronized to dispatch_once

Many legacy projects still use synchronized singleton accessors. Migration is typically straightforward:

  1. Keep API method unchanged.
  2. Replace synchronized block with dispatch_once.
  3. Run concurrent access tests.
  4. Verify no hidden side effects depend on repeated initialization.

This improves readability and aligns with current platform conventions.

Concurrency Testing Pattern

A practical test is to fetch singleton from many concurrent queues and assert all references are identical.

objective-c
1NSMutableArray *instances = [NSMutableArray array];
2dispatch_group_t group = dispatch_group_create();
3
4for (int i = 0; i < 1000; i++) {
5    dispatch_group_async(group, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
6        id obj = [MyManager sharedInstance];
7        @synchronized(instances) {
8            [instances addObject:obj];
9        }
10    });
11}
12
13dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

Then validate that every stored pointer equals the first one.

@synchronized Beyond Singletons

@synchronized still has valid uses for protecting mutable shared state. But for high-contention systems, lower-level locking primitives can offer better performance. Use @synchronized when clarity matters more than micro-optimization and contention is moderate.

Common Pitfalls

  • Using @synchronized for singleton creation when dispatch_once is more direct.
  • Locking large expensive code blocks and creating unnecessary contention.
  • Mixing lock objects for the same shared state.
  • Hiding side effects in singleton accessor methods.
  • Skipping concurrency tests after changing initialization logic.

Summary

  • '@synchronized provides mutual exclusion around Objective-C critical sections.'
  • It can make singleton lazy initialization safe but is not the best modern singleton primitive.
  • 'dispatch_once is preferred for exactly-once singleton creation.'
  • Keep lock scope small and lock object strategy consistent.
  • Validate thread safety with explicit concurrent access tests.

Course illustration
Course illustration

All Rights Reserved.