Thread Safe Singleton

There are two thread safety cases to consider

  • during initialization of the singleton instance
  • during reads and writes to the instance (internal variables)

Initialization

In objective-c, we can use dispatch_once to ensure that it’s only created once.

+ (id)sharedManager {
    static MyManager *sharedMyManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedMyManager = [[self alloc] init];
    });
    return sharedMyManager;
}

Global variables are guaranteed to be initialized in an atomic fashion.

Define a singleton

private let _sharedManager = PhotoManager()

class PhotoManager {
  class var sharedManager: PhotoManager {
    return _sharedManager
  }
}
class NetworkManager {
    static let shared = NetworkManager(baseURL: API.baseURL)

    let baseURL: URL

    private init(baseURL: URL) {
        self.baseURL = baseURL
    }

}
class NetworkManager {
    private static var sharedNetworkManager = NetworkManager()

    let baseURL: URL

    // Initialization

    private init(baseURL: URL) {
        self.baseURL = baseURL
    }

    // MARK: - Accessors

    class func shared() -> NetworkManager {
        return sharedNetworkManager
    }
}

Readers-Writers Problem

  • Any variable declared with the let keyword is considered thread-safe
  • Any variable declared using var, and mutable collection types are thread-unsafe

GCD provides an elegant solution of creating a read/write lock using dispatch barriers. Dispatch barriers are a group of functions acting as a serial-style bottleneck when working with concurrent queues.

  • Don't use barriers in global queues as these queues are shared resources.
  • Using barriers in a custom serial queue is redundant as it already executes serially.
  • Using barriers in custom concurrent queue is a great choice for handling thread safety in atomic of critical areas of code
fileprivate let concurrentQueue = DispatchQueue(label: "com.mydoc.photoQueue", attributes: .concurrent)

// Getter
var photos: [Photo] {
    var photosCopy: [Photo]!
    concurrentPhotoQueue.sync {
      photosCopy = self.photos
    }
    return photosCopy
}

// Setter
func addPhoto(_ photo: Photo) {
  concurrentQueue.async(flags: .barrier) { [weak self] in
    guard let strongSelf = self else { return }
    storngSelf.photos.append(photo) 
  }
}

The Deallocation Problem

  • UI objects should be deallocated on the main thread
  • Since it’s common that a secondary thread, operation, or block retains the caller, this is very easy to get wrong and quite hard to find/fix.

Consistent use of weak and not accessing ivars in async blocks/operations helps.

results matching ""

    No results matching ""