My code is not printing in the right order. I think its a problem with the way I'm using dispatch to main. I want to pass in a boss ID. Find the User with that ID then get their SubscribedBosses Array. For every ID in that array query that user and return that users screenName. Append those screenName to the userArray. After that Complete the GetBossSubs function and return userArray (array of screenNames).
Right now result of the .onAppear
is running before the function in .onAppear is actually completed. User Appended
prints before Found TestUserName
static func getBossSubs(bossID: String, completion: @escaping (Result<UserNames, Error>) ->
()) {
let pred = NSPredicate(format: "uniqueID = %@", bossID)
let sort = NSSortDescriptor(key: "creationDate", ascending: false)
let query = CKQuery(recordType: RecordType.Users, predicate: pred)
query.sortDescriptors = [sort]
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["subscribedBosses"]
operation.resultsLimit = 50
operation.recordFetchedBlock = { record in
DispatchQueue.main.async {
guard let subs = record["subscribedBosses"] as? [String] else {
print("Error at screenName")
completion(.failure(CloudKitHelperError.castFailure))
return
}
let userArray = UserNames() //Error that it should be a LET is here.
for boss in subs{
CloudKitHelper.getBossScreenName(bossID: boss) { (result) in
switch result{
case .success(let name):
userArray.names.append(name) //works fine
print("Found \(userArray.names)") //Prints a name
case .failure(let er):
completion(.failure(er))
}
}
}
print("does this run?") // Only runs if No Name
completion(.success(userArray)) // contains no name or doesn't run?
}
}
operation.queryCompletionBlock = { (_, err) in
DispatchQueue.main.async {
if let err = err {
completion(.failure(err))
return
}
}
}
CKContainer.default().publicCloudDatabase.add(operation)
}
I call the code like this:
.onAppear {
// MARK: - fetch from CloudKit
self.userList.names = []
let myUserID = UserDefaults.standard.string(forKey: self.signInWithAppleManager.userIdentifierKey)!
// get my subs projects
CloudKitHelper.getBossSubs(bossID: myUserID) { (results) in
switch results{
case .success(let user):
print("Users appended")
self.userList.names = user.names
case .failure(let er):
print(er.localizedDescription)
}
}
}
This is because you run an asynchronous function inside another asynchronous function.
// this just *starts* a series of asynchronous functions, `result` is not yet available
for boss in subs {
CloudKitHelper.getBossScreenName(bossID: boss) { result in
switch result {
case .success(let name):
userArray.names.append(name) // works fine
print("Found \(userArray.names)") // Prints a name
case .failure(let er):
completion(.failure(er))
}
}
}
// this will run before any `CloudKitHelper.getBossScreenName` finishes
completion(.success(userArray))
A possible solution may be to check if all CloudKitHelper.getBossScreenName
functions finish (eg. by checking the size of the userArray
) and only then return the completion:
for boss in subs {
CloudKitHelper.getBossScreenName(bossID: boss) { result in
switch result {
case .success(let name):
userArray.names.append(name)
if userArray.names.count == subs.count {
completion(.success(userArray)) // complete only when all functions finish
}
case .failure(let er):
completion(.failure(er))
}
}
}
// do not call completion here, wait for all functions to finish
// completion(.success(userArray))