I'm new to Swift, and asynchronous code in general, so tell me if this is way off. Basically I want to:
This clearly isn't useful in itself, but as a principle it will help me to understand the asynchronous code operation, and how to trigger actions on their completion.
// In ViewController Swift file:
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
readDatabase()
}
@IBOutlet weak var myLabel: UILabel!
}
let VC=ViewController()
//In Another Swift file:
func readDatabase() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "myRecord", predicate: predicate)
let container = CKContainer.default()
let privateDB = container.privateCloudDatabase
privateDB.perform(query, inZoneWith:nil) { (allRecs, err) in
VC.myLabel.text = ("\(allRecs?.count) records retreived")
/*
ERROR OCCURS IN LINE ABOVE:
CONSOLE: fatal error: unexpectedly found nil while unwrapping an Optional value
BY CODE LINE: Thread 8:EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
*/
}
}
I'm able to set the text field from within the viewDidLoad function, so why not from a function called within that function?
A few other things I've tried:
These both create the same problem as above.
Yes, I know there isn't any error handling in .perform, and yes there are records. If I trigger the setting of the UILabel text to the record count manually a few seconds after the view has loaded, it works fine.
So the question is... How do I use the completion of the database read as a trigger to load attributes of the records to the view?
Thanks
What actually happened here was that VC
was created globally, but never presented - since loadView
was never called for it, myLabel
didn't exist and, being a force unwrapped property, caused a crash when it was referenced
Got it, the solution looks like this:
// In ViewController Swift file:
typealias CompletionHandler = (_ recCount:Int,_ err:Error?) -> Void
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
readDatabase(completionHandler: { (recCount,success) -> Void in
if err == nil {
self.myLabel.text = "\(recCount) records loaded"
} else {
self.myLabel.text = "load failed: \(err)"
}
})
}
@IBOutlet weak var myLabel: UILabel!
}
//In Another Swift file:
func readDatabase() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "myRecord", predicate: predicate)
let container = CKContainer.default()
let privateDB = container.privateCloudDatabase
privateDB.perform(query, inZoneWith:nil) { (allRecs, err) in
if let recCount = allRecs?.count {
completionHandler(recCount,err)
} else {
completionHandler(0,err)
}
}
}
The difference between this and the original is that this uses the CompletionHandler typealias in the function call for loading the database records, which returns the count of records and an optional error.
The completion operation can now live in the ViewController class and access the UILabel using self.myLabel, which solves the error that was occurring earlier, while keeping the database loading code separate to the ViewController class.
This version of the code also has basic error handling.