8

I have a singleton class as so:

class Database {
   static let instance:Database = Database()
   private var db: Connection?

   private init(){
      do {
        db = try Connection("\(path)/SalesPresenterDatabase.sqlite3")
        }catch{print(error)}
   }
}

Now I access this class using Database.instance.xxxxxx to perform a function within the class. However when I access the instance from another thread it throws bizarre results as if its trying to create another instance. Should I be referencing the instance in the same thread?

To clarify the bizarre results show database I/o errors because of two instances trying to access the db at once

Update
please see this question for more info on the database code: Using transactions to insert is throwing errors Sqlite.swift

Community
  • 1
  • 1
Richard Thompson
  • 404
  • 8
  • 23
  • 8
    Swift singleton creation is thread safe. This doesn't meant that the functions you call in the singleton instance will be magically thread safe. You need to use something like a semaphore, a serial dispatch queue or an operation queue if you need to make functions thread safe – Paulw11 Apr 13 '17 at 13:33
  • `However when I access the instance from another thread it throws bizarre results as if its trying to create another instance` I don't think so, given the current definition of your `Database` class, you **cannot** create 2 instances. However maybe there's more code into that class that you are not showing. BTW: one last refinement to your class: you should declare it as `final` to prevent subclassing. – Luca Angeletti Apr 13 '17 at 13:38
  • The additional code in the class shows an **addRow** function. This function gets called in an alamofire completion block which then throws the error where it can't insert data. Interestingly though if I remove all calls to my Database.instance. singleton and let the problematic bit of code run it first, it will work fine... – Richard Thompson Apr 13 '17 at 13:43
  • please see this question for more info on the database code: http://stackoverflow.com/questions/43388443/using-transactions-to-insert-is-throwing-errors-sqlite-swift – Richard Thompson Apr 13 '17 at 13:44
  • @RichardThompson you can use FMDB and use his `FMDatabaseQueue` for thread safe access to your db – Reinier Melian Apr 13 '17 at 13:58
  • Thanks for the advise I'll look into this now. I've managed to find a work around by just creating a new instance of the class and referencing the **addRows** function. Not ideal though.... – Richard Thompson Apr 13 '17 at 14:01
  • You can also use [GRDB](https://github.com/groue/GRDB.swift). it has robust threading support. It will provide as many guarantees as FMDB, but without Objective-C. – Gwendal Roué Apr 14 '17 at 03:16

3 Answers3

6
class var shareInstance: ClassName {

    get {
        struct Static {
            static var instance: ClassName? = nil
            static var token: dispatch_once_t = 0
        }
        dispatch_once(&Static.token, {
            Static.instance = ClassName()
        })
        return Static.instance!
    }
}

USE: let object:ClassName = ClassName.shareInstance

Swift 3.0

class ClassName {
  static let sharedInstance: ClassName = { ClassName()} ()
}

USE: let object:ClassName = ClassName.shareInstance

Dhananjay Patil
  • 442
  • 3
  • 10
3

In Swift 3.0 add a private init to prevent others from using the default () initializer.

class ClassName {
  static let sharedInstance = ClassName()
  private init() {} //This prevents others from using the default '()' initializer for this class.
}
Richard Hope
  • 251
  • 3
  • 6
1

Singleton thread class.

final public class SettingsThreadSafe {

   public static let shared = SettingsThreadSafe()

   private let concurrentQueue = DispatchQueue(label: "com.appname.typeOfQueueAndUse", attributes: .concurrent)
   private var settings: [String: Any] = ["Theme": "Dark",
                                       "MaxConsurrentDownloads": 4]

   private init() {}

   public func string(forKey key: String) -> String? {
       var result: String?
       concurrentQueue.sync {
           result = self.settings[key] as? String
       }
       return result
   }

   public func int(forKey key: String) -> Int? {
       var result: Int?
       concurrentQueue.sync {
           result = self.settings[key] as? Int
       }
       return result
   }

   public func set(value: Any, forKey key: String) {
       concurrentQueue.async( flags: .barrier ) {
           self.settings[key] = value
       }
   }
}

Unit to test the singleton class.

func testConcurrentUsage() {
    let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
    
    let expect = expectation(description: "Using SettingsThreadSafe.shared from multiple threads shall succeed")
    
    let callCount = 300
    for callIndex in 1...callCount {
        concurrentQueue.async {
            SettingsThreadSafe.shared.set(value: callIndex, forKey: String(callIndex))
        }
    }
    
    while SettingsThreadSafe.shared.int(forKey: String(callCount)) != callCount {
        // nop
    }
    
    expect.fulfill()
    waitForExpectations(timeout: 5) { (error) in
        XCTAssertNil(error, "Test expectation failed")
    }
}
Anit Kumar
  • 8,075
  • 1
  • 24
  • 27