Search code examples
iosobjective-ccloudkit

CloudKit CKError extension not available in Objective-C?


I read somewhere here that CKError is not available in Objective-C, and I concur. For instance, this extension is available in Swift.

@available(OSX 10.10, iOS 8.0, watchOS 3.0, *)
extension CKError {

    /// Retrieve partial error results associated by item ID.
    public var partialErrorsByItemID: [AnyHashable : Error]? { get }

    /// The original CKRecord object that you used as the basis for
    /// making your changes.
    public var ancestorRecord: CKRecord? { get }

    /// The CKRecord object that was found on the server. Use this
    /// record as the basis for merging your changes.
    public var serverRecord: CKRecord? { get }

    /// The CKRecord object that you tried to save. This record is based
    /// on the record in the CKRecordChangedErrorAncestorRecordKey key
    /// but contains the additional changes you made.
    public var clientRecord: CKRecord? { get }

    /// The number of seconds after which you may retry a request. This
    /// key may be included in an error of type
    /// `CKErrorServiceUnavailable` or `CKErrorRequestRateLimited`.
    public var retryAfterSeconds: Double? { get }
}

The problem is that I need these objects in my Objective-C project. I've somehow (I believe) managed to get the partialErrorsByItemID in Objective-C by making a category for NSError and a little comprehension of the documentation of CKError.h, like so:

CKErrorCode ckErrorCode = (CKErrorCode) _code;
if (ckErrorCode == CKErrorPartialFailure) {
    // When a CKErrorPartialFailure happens this key will be set in the error's userInfo dictionary.
    // The value of this key will be a dictionary, and the values will be errors for individual items with the keys being the item IDs that failed.

    NSDictionary *dicError = _userInfo;

    if ([dicError objectForKey:CKPartialErrorsByItemIDKey] != nil) {

        NSDictionary *dic = (NSDictionary *)[dicError objectForKey:CKPartialErrorsByItemIDKey];

        for (NSString* key in dic) {
            NSError *newError = dic[key];
            if (code == newError.code) {
                match = YES;
            }
        }

    } else {
        return NO;
    }
}

But again, my problem is how to get the objects serverRecord and the clientRecord. Any idea?


Solution

  • Here's an Objective-C category that replicates most of the CKError structure of Swift. I didn't add errorCode, localizedDescription or errorUserInfo since NSError already provides those as code, localizedDescription, and userInfo.

    CloudKitExtensions.h

    #import <CloudKit/CloudKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    extern const double UnknownRetrySeconds;
    
    @interface NSError (CKError)
    
    - (NSDictionary<id, NSError *> * _Nullable)partialErrorsByItemID;
    - (CKRecord * _Nullable)ancestorRecord;
    - (CKRecord * _Nullable)clientRecord;
    - (CKRecord * _Nullable)serverRecord;
    - (double)retryAfterSeconds; // returns UnknownRetrySeconds if not available
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    CloudKitExtensions.m

    #import "CloudKitExtensions.h"
    
    const double UnknownRetrySeconds = -1;
    
    @implementation NSError (CKError)
    
    - (NSDictionary<id, NSError *> * _Nullable)partialErrorsByItemID {
        if ([self.domain isEqualToString:CKErrorDomain] && self.code == CKErrorPartialFailure) {
            return self.userInfo[CKPartialErrorsByItemIDKey];
        } else {
            return nil;
        }
    }
    
    - (CKRecord * _Nullable)ancestorRecord {
        if ([self.domain isEqualToString:CKErrorDomain] && self.code == CKErrorServerRecordChanged) {
            return self.userInfo[CKRecordChangedErrorAncestorRecordKey];
        } else {
            return nil;
        }
    }
    
    - (CKRecord * _Nullable)clientRecord {
        if ([self.domain isEqualToString:CKErrorDomain] && self.code == CKErrorServerRecordChanged) {
            return self.userInfo[CKRecordChangedErrorClientRecordKey];
        } else {
            return nil;
        }
    }
    
    - (CKRecord * _Nullable)serverRecord {
        if ([self.domain isEqualToString:CKErrorDomain] && self.code == CKErrorServerRecordChanged) {
            return self.userInfo[CKRecordChangedErrorServerRecordKey];
        } else {
            return nil;
        }
    }
    
    - (double)retryAfterSeconds {
        if ([self.domain isEqualToString:CKErrorDomain]) {
            NSNumber *delayVal = self.userInfo[CKErrorRetryAfterKey];
            return delayVal ? [delayVal doubleValue] : UnknownRetrySeconds;
        } else {
            return UnknownRetrySeconds;
        }
    }
    
    @end