Unable fetch children record from CloudKit outside performQuery

Here's my Model


import Cocoa
import CloudKit

@objc(LedgerAccount)

@objcMembers class LedgerAccount: NSObject {
    
    var recordID: CKRecordID
    var displayName: String
    var ledgerType: String
    var balance: Double
    var balanceMetal: Double
    var city: String
    
    init(recordID: CKRecordID, displayName: String, ledgerType: String, balance: Double, balanceMetal: Double, city: String) {
        
        self.recordID = recordID
        self.displayName = displayName
        self.ledgerType = ledgerType
        self.balance = balance
        self.balanceMetal = balanceMetal
        self.city = city
        
    }
    
}

All records except city are from same LedgerAccount recordType in CloudKit.

The City record is from its Child recordType called LedgerAddresseses recordType.


Here's my API.


import Cocoa
import CloudKit

class ContactsAPI {
    
    let privateData = CKContainer.default().privateCloudDatabase
    let branch = CKRecordZone(zoneName: "Master")
    
    var contacts: [LedgerAccount] = []
    
    func fetchContacts(completionHandler: @escaping ([LedgerAccount]) -> Void) {
        
        let contactsPredicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "LedgerAccount", predicate: contactsPredicate)
        
        
        privateData.perform(query, inZoneWith: branch.zoneID) { [unowned self] (results, error) -> Void in
            
            if error != nil {
                print("Error: \(String(describing: error))")
            } else {
                
                DispatchQueue.main.async {
                    
                    for result in results! {
                        
                        let recordID = result["recordID"] as! CKRecordID
                        let displayName = result["DisplayName"] as! String
                        let ledgerType = result["LedgerType"] as! String
                        let balance = result["Balance"] as! Double
                        let balanceMetal = result["BalanceMetal"] as! Double
                        var city: String = ""

                        //fetching address
                        let addressPredicate = NSPredicate(format: "LedgerAccount = %@ && AddressLabel='Primary'", recordID)
                        let addressQuery = CKQuery(recordType: "LedgerAddresses", predicate: addressPredicate)
                        self.privateData.perform(addressQuery, inZoneWith: self.branch.zoneID) { (results, error) in
                            if error != nil {
                                
                            } else {
                                for addressResult in results! {
                                    city = addressResult["City"] as! String
                                    print(city)
                                }
                            }

                        } // end of fetch addresses
                        
                        print(city)

                        let contact = LedgerAccount(recordID: recordID,
                                                    displayName: displayName,
                                                    ledgerType: ledgerType,
                                                    balance: balance,
                                                    balanceMetal: balanceMetal,
                                                    city: city)
                        
                        self.contacts.append(contact)


                    }
                    completionHandler(self.contacts)
                }

            }
            
        }
        
    }  // end of fetchContacts
    
    
}

At line 43, I get the city name, but at line 49 it returns null.

Answered by ManuelMB in 326480022

perform(_:inZoneWith:completionHandler:) Searches asynchronously.


If you need the city name in line 43 you need to use a Semaphore.


import Cocoa
import CloudKit

class ContactsAPI {
    
    let privateData = CKContainer.default().privateCloudDatabase
    let branch = CKRecordZone(zoneName: "Master")
    
    var contacts: [LedgerAccount] = []
    
    func fetchContacts(completionHandler: @escaping ([LedgerAccount]) -> Void) {
        
        let contactsPredicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "LedgerAccount", predicate: contactsPredicate)
        
        
        privateData.perform(query, inZoneWith: branch.zoneID) { [unowned self] (results, error) -> Void in
            
            if error != nil {
                print("Error: \(String(describing: error))")
            } else {
                
                DispatchQueue.main.async {
                    
                    for result in results! {
                        
                        let semaphore = DispatchSemaphore(value: 0)
                        
                        let recordID = result["recordID"] as! CKRecordID
                        let displayName = result["DisplayName"] as! String
                        let ledgerType = result["LedgerType"] as! String
                        let balance = result["Balance"] as! Double
                        let balanceMetal = result["BalanceMetal"] as! Double
                        var city: String = ""
                        
                        //fetching address
                        let addressPredicate = NSPredicate(format: "LedgerAccount = %@ && AddressLabel='Primary'", recordID)
                        let addressQuery = CKQuery(recordType: "LedgerAddresses", predicate: addressPredicate)
                        self.privateData.perform(addressQuery, inZoneWith: self.branch.zoneID) { (results, error) in
                            if error != nil {
                                
                            } else {
                                for addressResult in results! {
                                    city = addressResult["City"] as! String
                                    print(city)
                                }
                            }
                            
                           semaphore.signal()
                            
                        } // end of fetch addresses
                        
                        
                        let _ = semaphore.wait(timeout: .distantFuture)
                        
                        print(city)
                        
                        let contact = LedgerAccount(recordID: recordID,
                                                    displayName: displayName,
                                                    ledgerType: ledgerType,
                                                    balance: balance,
                                                    balanceMetal: balanceMetal,
                                                    city: city)
                        
                        self.contacts.append(contact)
                    }
                    completionHandler(self.contacts)
                }
            }
        }
    }  // end of fetchContacts
}
Accepted Answer

perform(_:inZoneWith:completionHandler:) Searches asynchronously.


If you need the city name in line 43 you need to use a Semaphore.


import Cocoa
import CloudKit

class ContactsAPI {
    
    let privateData = CKContainer.default().privateCloudDatabase
    let branch = CKRecordZone(zoneName: "Master")
    
    var contacts: [LedgerAccount] = []
    
    func fetchContacts(completionHandler: @escaping ([LedgerAccount]) -> Void) {
        
        let contactsPredicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "LedgerAccount", predicate: contactsPredicate)
        
        
        privateData.perform(query, inZoneWith: branch.zoneID) { [unowned self] (results, error) -> Void in
            
            if error != nil {
                print("Error: \(String(describing: error))")
            } else {
                
                DispatchQueue.main.async {
                    
                    for result in results! {
                        
                        let semaphore = DispatchSemaphore(value: 0)
                        
                        let recordID = result["recordID"] as! CKRecordID
                        let displayName = result["DisplayName"] as! String
                        let ledgerType = result["LedgerType"] as! String
                        let balance = result["Balance"] as! Double
                        let balanceMetal = result["BalanceMetal"] as! Double
                        var city: String = ""
                        
                        //fetching address
                        let addressPredicate = NSPredicate(format: "LedgerAccount = %@ && AddressLabel='Primary'", recordID)
                        let addressQuery = CKQuery(recordType: "LedgerAddresses", predicate: addressPredicate)
                        self.privateData.perform(addressQuery, inZoneWith: self.branch.zoneID) { (results, error) in
                            if error != nil {
                                
                            } else {
                                for addressResult in results! {
                                    city = addressResult["City"] as! String
                                    print(city)
                                }
                            }
                            
                           semaphore.signal()
                            
                        } // end of fetch addresses
                        
                        
                        let _ = semaphore.wait(timeout: .distantFuture)
                        
                        print(city)
                        
                        let contact = LedgerAccount(recordID: recordID,
                                                    displayName: displayName,
                                                    ledgerType: ledgerType,
                                                    balance: balance,
                                                    balanceMetal: balanceMetal,
                                                    city: city)
                        
                        self.contacts.append(contact)
                    }
                    completionHandler(self.contacts)
                }
            }
        }
    }  // end of fetchContacts
}

Got it. But is it possible achieve same with completion handler.

You can wait to finish the first query and them do the second one.


import Cocoa
import CloudKit

class ContactsAPI {
    let privateData = CKContainer.default().privateCloudDatabase
    let branch = CKRecordZone(zoneName: "Master")
    
    var contacts: [LedgerAccount] = []
    
    func fetchContacts(completionHandler: @escaping ([LedgerAccount]) -> Void) {
        
        let contactsPredicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "LedgerAccount", predicate: contactsPredicate)
        
        
        privateData.perform(query, inZoneWith: branch.zoneID) { [unowned self] (results, error) -> Void in
            
            if error != nil {
                print("Error: \(String(describing: error))")
            } else {
                
                DispatchQueue.main.async {
                    
                    for result in results! {
                        let recordID = result["recordID"] as! CKRecord.ID
                        let displayName = result["DisplayName"] as! String
                        let ledgerType = result["LedgerType"] as! String
                        let balance = result["Balance"] as! Double
                        let balanceMetal = result["BalanceMetal"] as! Double
                        let city: String = ""
                        
                        
                        let contact = LedgerAccount(recordID: recordID,
                                                    displayName: displayName,
                                                    ledgerType: ledgerType,
                                                    balance: balance,
                                                    balanceMetal: balanceMetal,
                                                    city: city)
                        
                        self.contacts.append(contact)
                    }
                    completionHandler(self.contacts)
                }
            }
        }
    }  // end of fetchContacts
}



import Foundation
import CloudKit

class LedgerAccount{
    var recordID: CKRecord
    var displayName: String
    var ledgerType: String
    var balance: Double
    var balanceMetal: Double
    var city: String
    init(recordID: CKRecord, displayName: String, ledgerType: String, balance: Double, balanceMetal: Double, city: String){
        self.recordID = recordID
        self.displayName = displayName
        self.ledgerType = ledgerType
        self.balance = balance
        self.balanceMetal = balanceMetal
        self.city = city
    }
}


import Cocoa
import CloudKit

class FetchContactsViewController: NSViewController {
    
    let contactsAPI = ContactsAPI()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        contactsAPI.fetchContacts(){ [weak self] (ledgerAccounts) in
            
            if let strongSelf = self{
                for ledgerAccount in ledgerAccounts{
                    //fetching address
                    
                    let recordID = ledgerAccount.recordID
                    
                    let addressPredicate = NSPredicate(format: "LedgerAccount = %@ && AddressLabel='Primary'", recordID)
                    let addressQuery = CKQuery(recordType: "LedgerAddresses", predicate: addressPredicate)
                    strongSelf.contactsAPI.privateData.perform(addressQuery, inZoneWith: strongSelf.contactsAPI.branch.zoneID) { (results, error) in
                        if error != nil {
                            
                        } else {
                            for addressResult in results! {
                                let city = addressResult["City"] as! String
                                print(city)
                            }
                        }
                        
                    } // end of fetch addresses
                }
            }
        }
    }
}

Thanks, Manuel

Unable fetch children record from CloudKit outside performQuery
 
 
Q