swiftdesign-patternssqlite.swift

Creating a global SQLite DB Connection with Singleton pattern in Swift


I detect whether a connection exists on a Singleton instance and if it doesn't it will open up a new connection on app start up. However, ideally I'd like for the open function to fire when the Singleton is first created without having to explicitly call it and assign it to some variable within the class.

The way I have the code now it defeats the purpose of having a shared Singleton because I just access the static connection object. When I attempt to change the static conn to an instance variable I get the error Instance member 'conn' cannot be used on type 'DBConnection'

Non-working code

class DBConnection {
    static let shared = DBConnection()
    var conn: Connection? = nil
    
    private init() { }
    
    static func open(dbPath: URL) throws {
        var dbConnections: [Connection] = []
        
        do {
            let dbConnection = try Connection("\(dbPath)/db.sqlite3")
            dbConnections.append(dbConnection)
            print("found connection, \(dbConnections)")
        } catch {
            throw SQLiteDBError.couldNotOpenConnection
        }
        
        self.conn = dbConnections[0]
        print("successfully opened connection")
    }
}

How can you call a private function within a Singleton class on init and assign it to some variable?

Current working code

class DBConnection {
    static let shared = DBConnection()
    static var conn: Connection?
    
    private init() { }
    
    static func open(dbPath: URL) throws {
        var dbConnections: [Connection] = []
        
        do {
            let dbConnection = try Connection("\(dbPath)/db.sqlite3")
            dbConnections.append(dbConnection)
            print("found connection, \(dbConnections)")
        } catch {
            throw SQLiteDBError.couldNotOpenConnection
        }
        
        self.conn = dbConnections[0]
        print("successfully opened connection")
    }
}

Main app init

@main
struct MyApp: App {
    
    init() {
        if DBConnection.conn != nil {
            do {
                try DBConnection.open(dbPath: FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask)[0])
            } catch {
                print("error")
            }
        } else {
            print("Connection already existed")
        }
    }
    ....
}

Solution

  • When I attempt to change the static conn to an instance variable I get the error Instance member 'conn' cannot be used on type 'DBConnection'

    That makes sense considering how you're using open:

    try DBConnection.open(dbPath: FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask)[0])
    

    Right now, open is a static method of DBConnection, which means that you can call it without needing an instance of the class. It's similar to a "class method" in Objective-C. But in your broken code, you have:

    var conn: Connection? = nil

    so conn is an instance variable, i.e. a variable that only exists in the context of an instance of the class. A quick fix would be to make open non-static:

    func open(dbPath: URL) throws {
    

    and then call it using the shared instance:

    try DBConnection.shared.open(dbPath: FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask)[0])
    

    (Note the shared added there.)

    However, ideally I'd like for the open function to fire when the Singleton is first created without having to explicitly call it and assign it to some variable within the class.

    You could call open from inside the initializer if you want the connection to open when the singleton is created.

    Another direction you could go is to leave conn and open both static so that open can access conn, and let the class itself be the singleton. Then you could eliminate the shared variable.

    However, I think the best option would be to do as described above, making conn and open non-static, and get rid of the shared instance and instead just keep track of the DBConnection object that you create.