user440309
user440309

Reputation: 63

Cloudkit error handling not being called if qualityOFService is missing

I am trying to handle Cloudkit errors. I read this post: CloudKit - full and complete error handling example but i have a few questions:

1.Why is the error handling not working if i dont set .qualityOFService? and is .userInitiated correct, as on the post mentioned it is set to .background?. Also, do i have to set it for the loadDataAgain()?

2.How can i make the error handling reusable that will take an error and another parameter(according to the viewcontroller that is called from)?

 @objc func loadData() {
    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: myRecordType.type, predicate: predicate)
    query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
    let queryOperation = CKQueryOperation(query: query)
    queryOperation.resultsLimit = 5
   //if the below line is missing errors will not be handled
    queryOperation.qualityOfService = .userInitiated
    queryOperation.recordFetchedBlock = { item in
        self.array.append(item)
    }
    queryOperation.queryCompletionBlock = { cursor, error in
        if error != nil{
          if let ckerror = error as? CKError {
            if ckerror.code == CKError.requestRateLimited {
                let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
                DispatchQueue.main.async {
                    Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.loadData), userInfo: nil, repeats: false)
                }
            } else if ckerror.code == CKError.zoneBusy {
                let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
                DispatchQueue.main.async {
                    Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.loadData), userInfo: nil, repeats: false)
                }
            } else if ckerror.code == CKError.limitExceeded {
                let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
                DispatchQueue.main.async {
                    Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.loadData), userInfo: nil, repeats: false)
                }
            } else if ckerror.code == CKError.notAuthenticated {
                //present relevant alert with action to reload ViewController
            } else if ckerror.code == CKError.networkFailure {
                //present relevant alert with action to reload ViewController
            } else if ckerror.code == CKError.networkUnavailable {
                //present relevant alert with action to reload ViewController
            } else if ckerror.code == CKError.quotaExceeded {
                //present relevant alert with action to reload ViewController
            } else if ckerror.code == CKError.partialFailure {
                //present relevant alert with action to reload ViewController
            } else if (ckerror.code == CKError.internalError || ckerror.code == CKError.serviceUnavailable) {
                //present relevant alert with action to reload ViewController
            }
        }
        }
        else{
            if cursor != nil {
                self.loadDataAgain(cursor!)
            }
        }
        OperationQueue.main.addOperation {
            self.tableView.reloadData()
        }
}

func loadDataAgain(_ cursor: CKQueryOperation.Cursor) {
    let queryOperation = CKQueryOperation(cursor: cursor)
    queryOperation.resultsLimit = 5

    queryOperation.recordFetchedBlock = { item in
        self.array.append(item)
    }
    queryOperation.queryCompletionBlock = { cursor, error in
        if error != nil{
            //have the same error handling as above
        }
        else{
            if cursor != nil {
                self.loadDataAgain(cursor!)
            }
        }
        OperationQueue.main.addOperation {
            self.tableView.reloadData()
        }
    }
    Database.share.publicDB.add(queryOperation)
}

Upvotes: 1

Views: 225

Answers (1)

Zoë Smith
Zoë Smith

Reputation: 1084

With respect to your first question, see this answer that suggests errors aren't reported for lower quality of service settings:

it's because [the] system is waiting for a better network condition, while [if] you set .default or .userInitiated the system expects a "real time" response

If you have a look at Apple's Energy Efficiency Guide for iOS Apps documentation section titled "About CloudKit and Quality of Service," Apple seems to confirm this approach, saying

CKOperation is a subclass of the NSOperation class. Although the NSOperation class has a default QoS level of NSQualityOfServiceBackground, CKOperation objects have a default QoS level of NSQualityOfServiceUtility. At this level, network requests are treated as discretionary when your app isn’t in use. On iPhones, discretionary operations are paused when Low Power Mode is enabled.

You'll need to explicitly set a different QOS for every CKOperation you create if you don't want the default .utility. If you find yourself doing this a lot with related operations, I'd suggest looking at CKOperationGroup and its default configuration property - if you set a QOS in the configuration it'll override the default in any CKOperations in the group.

Upvotes: 2

Related Questions