Search code examples
iosdatabaseparse-platform

Unable to call "findObjectsInBackground" in parse in swift


In my following code when i am calling openDatabase() it is showing fatal error as pathToDatabase is nil.
Actually when i am calling findObjectsInBackground i am unable to enter the block. so that i am getting pathToDatabase as nil ? Please help me.

import UIKit  
import Parse  

class existDatabase: NSObject {

    var databaseFileName = ""
    var pathToDatabase: String!
    var database: FMDatabase!
    var userAccessCode = ""


    override init()
    {
        super.init()


        if let accesscode = USERDEFAULTS.string(forKey: "UserAccessCode"){

            let query: PFQuery = PFQuery(className: "AccessCode")
            query.whereKey("RegisterCode", equalTo: accesscode)
            query.findObjectsInBackground { (objects, err) in
                if err == nil{
                    if let users = objects{
                        if users.isEmpty {

                        }
                        else{

                            for post in users{
                                if let surgery = post["SurgeryType"] as? String{

                                    if surgery == "Trigger Finger"{
                                        self.databaseFileName = "thumbtriggerfinger"
                                        self.pathToDatabase = Bundle.main.path(forResource: "thumbtriggerfinger", ofType: "db")
                                    }
                                }
                            }
                        }
                    }
                }
                else{
                    print(err?.localizedDescription)
                }
            }

        }
    }

    func openDatabase() -> Bool {
        if database == nil {

            if FileManager.default.fileExists(atPath: pathToDatabase) {
                database = FMDatabase(path: pathToDatabase)
            }
        }

        if database != nil {
            if database.open() {
                return true
            }
        }

        return false
    }
}

Solution

  • Presumably you have code something like

     let x = existDatabase()
     x.openDatabase()
    

    The problem is that you have an asynchronous block in your initialiser, which means that the object won't be fully initialised when the initialiser returns; the Parse background fetch is not yet complete.

    You get a crash because the implicitly unwrapped optional is nil. The fact that you needed to use an implicitly unwrapped optional is your first warning sign. You should be able to use non-optionals for a property set in an initialiser, but you can't because the properties are set in the closure.

    Having an asynchronous operation in an initialiser is rarely a good design.

    I would suggest you restructure your code so that openDatabase performs the asynchronous operation and accepts a completion hander.

    Also, stylistically, class names should start with a capital letter, and are typically nouns, not verbs.

    class DatabaseManager {
    
        static database(for accessCode: String, completion: @escaping (FMDatabase?,Error?)) {
            let query: PFQuery = PFQuery(className: "AccessCode")
            query.whereKey("RegisterCode", equalTo: accessCode)
            query.findObjectsInBackground { (objects, err) in
                if err == nil{
                    if let users = objects, !users.isEmpty {
                        for post in users{
                            if let surgery = post["SurgeryType"] as? String{
                                if surgery == "Trigger Finger"{
                                    let pathToDatabase = Bundle.main.path(forResource: "thumbtriggerfinger", ofType: "db")
                                    if FileManager.default.fileExists(atPath: pathToDatabase) {
                                        database = FMDatabase(path: pathToDatabase)
                                        if database.open() {
                                            completion(database,nil)
                                        } else {
                                            completion(nil, nil)
                                        }
                                    break
                                    }
                                }
                            }
                        }
                    } else {
                        completion(nil,nil)
                    }
                }
                else{
                    completion(nil, err)
                }
            }
        }
    }
    

    Call it using:

    if let accesscode = USERDEFAULTS.string(forKey: "UserAccessCode"){
        DatabaseManager.database(for: accesscode) { (database, error) in {
            guard error == nil else {
                print("error: (error!.localalizedDescription)")
                return
            } 
            guard let db = database else {
                print("No database")
                return
            }
            // Do something with db
        }
    }