Reputation: 966
Apple's sample project CloudKitShare at Sharing CloudKit Data with Other iCloud Users has a line of code that checks whether a parameter 'error' conforming to the Error protocol passed to a function that handles CloudKit errors can be cast to NSError at the beginning of the function, and if not, the function returns nil.
Here is the code that does the check at the very beginning of the function:
guard let nsError = error as NSError? else {
return nil
}
The return value for the function is 'CKError?'.
What is the purpose of this type check? A comment above the function declaration says:
// Return nil: No error or the error is ignorable.
How do I check why the case fails? When I print the 'error' argument to the debug window, it seems to always print that the argument is an Optional of type Error.
Code:
print(type(of: error))
Debug window:
Optional<Error>
Following is the relevant code. I narrowed down all the possible reasons that the function returns a nil:
Here is all the relevant code:
// Return nil: No error or the error is ignorable.
// Return a CKError: There's an error. The caller needs to determine how to handle it.
//
func handleCloudKitError(_ error: Error?, operation: CloudKitOperationType,
affectedObjects: [Any]? = nil, alert: Bool = false) -> CKError? {
// nsError == nil: Everything goes well and the caller can continue.
//
guard let nsError = error as NSError? else {
return nil
}
// Partial errors can happen when fetching or changing the database.
//
// When modifying zones, records, and subscriptions, .serverRecordChanged may happen if
// the other peer changes the item at the same time. In that case, retrieve the first
// CKError object and return to the caller.
//
// In the case of .fetchRecords and fetchChanges, the other peer might delete the specified
// items or zones, so they might not exist in the database (.unknownItem or .zoneNotFound).
//
if let partialError = nsError.userInfo[CKPartialErrorsByItemIDKey] as? NSDictionary {
// If the error doesn't affect the affectedObjects, ignore it.
// If it does, only handle the first error.
//
let errors = affectedObjects?.map({ partialError[$0] }).filter({ $0 != nil })
guard let ckError = errors?.first as? CKError else {
return nil
}
return handlePartialError(ckError, operation: operation, alert: alert)
}
// In the case of fetching changes:
// .changeTokenExpired: Return for callers to refetch with a nil server token.
// .zoneNotFound: Return for callers to switch zones because the current zone doesn't exist.
// .partialFailure: zoneNotFound triggers a partial error as well.
//
if operation == .fetchChanges {
if let ckError = error as? CKError {
if ckError.code == .changeTokenExpired || ckError.code == .zoneNotFound {
return ckError
}
}
}
// If the client requires an alert, alert the error, or append the error message to an existing alert.
//
if alert {
alertError(code: nsError.code, domain: nsError.domain,
message: nsError.localizedDescription, operation: operation)
}
print("\(operation.rawValue) operation error: \(nsError)")
return error as? CKError
}
private func handlePartialError(_ error: CKError, operation: CloudKitOperationType,
alert: Bool = false) -> CKError? {
// Items don't exist. Silently ignore the error for the .delete... operation.
//
if operation == .deleteZones || operation == .deleteRecords || operation == .deleteSubscriptions {
if error.code == .unknownItem {
return nil
}
}
if error.code == .serverRecordChanged {
print("Server record changed. Consider using serverRecord and ignore this error!")
} else if error.code == .zoneNotFound {
print("Zone not found. May have been deleted. Probably ignore!")
} else if error.code == .unknownItem {
print("Unknown item. May have been deleted. Probably ignore!")
} else if error.code == .batchRequestFailed {
print("Atomic failure!")
} else {
if alert {
alertError(code: error.errorCode, domain: CKError.errorDomain,
message: error.localizedDescription, operation: operation)
}
print("\(operation.rawValue) operation error: \(error)")
}
return error
}
private func alertError(code: Int, domain: String, message: String, operation: CloudKitOperationType) {
print("#", #function)
DispatchQueue.main.async {
guard let scene = UIApplication.shared.connectedScenes.first,
let sceneDeleate = scene.delegate as? SceneDelegate,
let viewController = sceneDeleate.window?.rootViewController else {
return
}
let message = "\(operation.rawValue) operation hit error.\n" +
"Error code: \(code)\n" + "Domain: \(domain)\n" + message
if let existingAlert = viewController.presentedViewController as? UIAlertController {
print("message:", existingAlert.message)
existingAlert.message = (existingAlert.message ?? "") + "\n\n\(message)"
return
}
let newAlert = UIAlertController(title: "CloudKit Operation Error!",
message: message, preferredStyle: .alert)
newAlert.addAction(UIAlertAction(title: "OK", style: .default))
print("message:", message)
viewController.present(newAlert, animated: true)
}
}
Upvotes: 0
Views: 88
Reputation: 2216
Yes, there could be a need to cast to an NSError conformance. NSError has attributes that the Swift Error
does not. If you're working with older codebases, or you want what an NSError has to offer, you may be dealing with them from time to time.
I feel like the example in the documentation is a little misleading. The error being cast into an NSError?
is an argument for a completionHandler
callback which we know nothing about. My assumption is that this documentation is either older, or it is geared toward people who deal with older codebases. If you're newer to Swift and you're not dealing with that kind of thing, I can see how it is confusing.
For the sake of completion though, Say you're working with a 3rd party library that was built 5 years ago. They will likely have plenty of NSErrors in their framework. Most of the Swift interface will expect to handle the Error
Protocol, but if you want any special detail off of the NSError
object, you first need to cast the Error to NSError if possible.
Upvotes: 1