2017-04-22 4 views
2

Ich suche nach allgemeinen Ratschlägen zum Umgang mit CloudKit Fehlern in Swift und habe Probleme, gute Beispiele online zu finden. Hier sind die Dinge, die ich frage mich:Handhabung von CloudKit Fehlern

1) Sollte ich für jeden einzelnen Fehlertyp jedes Mal die Möglichkeit für einen Fehler auftritt, oder ist das nicht wirklich notwendig?

2) Ich habe gelesen, dass eine übliche Methode zur Behandlung von CloudKit-Fehlern darin besteht, die Operation nach dem von der Fehlermeldung bereitgestellten Zeitintervall erneut auszuführen. Sollte diese Wiederholung grundsätzlich meine Standardprozedur für alle Fehler sein?

3) Führen verschiedene CloudKit-Vorgänge (Speichern, Abrufen usw.) zu unterschiedlichen Fehlertypen oder gibt es einen Standardsatz von CloudKit-Fehlern?

Vielen Dank im Voraus! Ich suche nur nach allgemeinen Informationen darüber, wie man mit der Fehlerbehandlung mit CloudKit umgehen kann, weil ich nicht wirklich sicher bin, wo ich anfangen soll.

Antwort

4

Ich habe eine CloudKit-Hilfe geschrieben, die die Verarbeitung von Fehlern erheblich vereinfacht. Dies ist nur ein Ausgangspunkt und es gibt viel mehr, was getan werden kann.

Der Hauptfokus dieses Helpers im aktuellen Zustand besteht darin, Fehler zu wiederholen, die nach einer entsprechenden Zeitüberschreitung erneut versucht werden sollten.

Sie müssen jedoch weiterhin Fehler beheben, die nicht wiederholt werden sollten, z. B. wenn der iCloud-Speicher des Benutzers voll ist. Selbst mit diesem Helper muss jeder Aufruf einer dieser Hilfsmethoden das Ergebnis richtig behandeln und möglicherweise einen Fehler an den Benutzer melden. Natürlich können Sie eine Hilfsmethode hinzufügen, die alle möglichen Fehlertypen überprüft und eine entsprechende Meldung anzeigt. Dann können alle Verwendungen des CloudKit-Codes diese eine Hilfsmethode aufrufen.

Dies deckt auch nur einige der möglichen Operationen ab. Sie möchten auch Unterstützung für andere Vorgänge hinzufügen. Schließlich behandelt dies noch keine Teilfehler. Das wäre eine weitere nützliche Verbesserung.

import Foundation 
import CloudKit 

public class CloudKitHelper { 
    private static func determineRetry(error: Error) -> Double? { 
     if let ckerror = error as? CKError { 
      switch ckerror { 
      case CKError.requestRateLimited, CKError.serviceUnavailable, CKError.zoneBusy, CKError.networkFailure: 
       let retry = ckerror.retryAfterSeconds ?? 3.0 

       return retry 
      default: 
       return nil 
      } 
     } else { 
      let nserror = error as NSError 
      if nserror.domain == NSCocoaErrorDomain { 
       if nserror.code == 4097 { 
        print("cloudd is dead") 

        return 6.0 
       } 
      } 

      print("Unexpected error: \(error)") 
     } 

     return nil 
    } 

    public static func modifyRecordZonesOperation(database: CKDatabase, recordZonesToSave: [CKRecordZone]?, recordZoneIDsToDelete: [CKRecordZoneID]?, modifyRecordZonesCompletionBlock: @escaping (([CKRecordZone]?, [CKRecordZoneID]?, Error?) -> Void)) { 
     let op = CKModifyRecordZonesOperation(recordZonesToSave: recordZonesToSave, recordZoneIDsToDelete: recordZoneIDsToDelete) 
     op.modifyRecordZonesCompletionBlock = { (savedRecordZones: [CKRecordZone]?, deletedRecordZoneIDs: [CKRecordZoneID]?, error: Error?) -> Void in 
      if let error = error { 
       if let delay = determineRetry(error: error) { 
        DispatchQueue.global().asyncAfter(deadline: .now() + delay) { 
         CloudKitHelper.modifyRecordZonesOperation(database: database, recordZonesToSave: recordZonesToSave, recordZoneIDsToDelete: recordZoneIDsToDelete, modifyRecordZonesCompletionBlock: modifyRecordZonesCompletionBlock) 
        } 
       } else { 
        modifyRecordZonesCompletionBlock(savedRecordZones, deletedRecordZoneIDs, error) 
       } 
      } else { 
       modifyRecordZonesCompletionBlock(savedRecordZones, deletedRecordZoneIDs, error) 
      } 
     } 
     database.add(op) 
    } 

    public static func modifyRecords(database: CKDatabase, records: [CKRecord], completion: @escaping (([CKRecord]?, Error?) -> Void)) { 
     CloudKitHelper.modifyAndDeleteRecords(database: database, records: records, recordIDs: nil) { (savedRecords, deletedRecords, error) in 
      completion(savedRecords, error) 
     } 
    } 

    public static func deleteRecords(database: CKDatabase, recordIDs: [CKRecordID], completion: @escaping (([CKRecordID]?, Error?) -> Void)) { 
     CloudKitHelper.modifyAndDeleteRecords(database: database, records: nil, recordIDs: recordIDs) { (savedRecords, deletedRecords, error) in 
      completion(deletedRecords, error) 
     } 
    } 

    public static func modifyAndDeleteRecords(database: CKDatabase, records: [CKRecord]?, recordIDs: [CKRecordID]?, completion: @escaping (([CKRecord]?, [CKRecordID]?, Error?) -> Void)) { 
     let op = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: recordIDs) 
     op.savePolicy = .allKeys 
     op.modifyRecordsCompletionBlock = { (savedRecords: [CKRecord]?, deletedRecordIDs: [CKRecordID]?, error: Error?) -> Void in 
      if let error = error { 
       if let delay = determineRetry(error: error) { 
        DispatchQueue.global().asyncAfter(deadline: .now() + delay) { 
         CloudKitHelper.modifyAndDeleteRecords(database: database, records: records, recordIDs: recordIDs, completion: completion) 
        } 
       } else { 
        completion(savedRecords, deletedRecordIDs, error) 
       } 
      } else { 
       completion(savedRecords, deletedRecordIDs, error) 
      } 
     } 
     database.add(op) 
    } 
} 
3

Ja, Sie möchten jeden Cloudkit-Aufruf auf Fehler überprüfen. Apple betont diesen Punkt in den Cloudkit-bezogenen WWDC-Videos.

Was Sie tun, wenn Sie einen Fehler feststellen, ist sehr unterschiedlich. Retry ist manchmal eine Option, aber manchmal nicht geeignet. Wenn Sie Batch-Vorgänge verwenden, erfordert das erneute Versuchen möglicherweise zusätzliche Arbeit, um nur die fehlgeschlagenen Datensätze zu extrahieren. Ja, manchmal möchten Sie vielleicht noch einmal versuchen, aber nein, Sie werden wahrscheinlich nicht jeden Vorgang wiederholen, der fehlschlägt.

Es gibt einen Satz von Fehlern, definiert in CKError.h. Aber du bekommst nicht immer einen CKError. Manchmal, besonders mit CKErrorPartialFailure, erhalten Sie einen Fehler auf oberster Ebene, der verschachtelte Fehler enthält, die Sie ebenfalls auspacken müssen. Wie von IOS 10, sieht die Liste der Fehler in CKError.h wie:

typedef NS_ENUM(NSInteger, CKErrorCode) { 
    CKErrorInternalError     = 1, /* CloudKit.framework encountered an error. This is a non-recoverable error. */ 
    CKErrorPartialFailure     = 2, /* Some items failed, but the operation succeeded overall. Check CKPartialErrorsByItemIDKey in the userInfo dictionary for more details. */ 
    CKErrorNetworkUnavailable    = 3, /* Network not available */ 
    CKErrorNetworkFailure     = 4, /* Network error (available but CFNetwork gave us an error) */ 
    CKErrorBadContainer     = 5, /* Un-provisioned or unauthorized container. Try provisioning the container before retrying the operation. */ 
    CKErrorServiceUnavailable    = 6, /* Service unavailable */ 
    CKErrorRequestRateLimited    = 7, /* Client is being rate limited */ 
    CKErrorMissingEntitlement    = 8, /* Missing entitlement */ 
    CKErrorNotAuthenticated    = 9, /* Not authenticated (writing without being logged in, no user record) */ 
    CKErrorPermissionFailure    = 10, /* Access failure (save, fetch, or shareAccept) */ 
    CKErrorUnknownItem     = 11, /* Record does not exist */ 
    CKErrorInvalidArguments    = 12, /* Bad client request (bad record graph, malformed predicate) */ 
    CKErrorResultsTruncated NS_DEPRECATED(10_10, 10_12, 8_0, 10_0, "Will not be returned") = 13, 
    CKErrorServerRecordChanged   = 14, /* The record was rejected because the version on the server was different */ 
    CKErrorServerRejectedRequest   = 15, /* The server rejected this request. This is a non-recoverable error */ 
    CKErrorAssetFileNotFound    = 16, /* Asset file was not found */ 
    CKErrorAssetFileModified    = 17, /* Asset file content was modified while being saved */ 
    CKErrorIncompatibleVersion   = 18, /* App version is less than the minimum allowed version */ 
    CKErrorConstraintViolation   = 19, /* The server rejected the request because there was a conflict with a unique field. */ 
    CKErrorOperationCancelled    = 20, /* A CKOperation was explicitly cancelled */ 
    CKErrorChangeTokenExpired    = 21, /* The previousServerChangeToken value is too old and the client must re-sync from scratch */ 
    CKErrorBatchRequestFailed    = 22, /* One of the items in this batch operation failed in a zone with atomic updates, so the entire batch was rejected. */ 
    CKErrorZoneBusy      = 23, /* The server is too busy to handle this zone operation. Try the operation again in a few seconds. */ 
    CKErrorBadDatabase     = 24, /* Operation could not be completed on the given database. Likely caused by attempting to modify zones in the public database. */ 
    CKErrorQuotaExceeded     = 25, /* Saving a record would exceed quota */ 
    CKErrorZoneNotFound     = 26, /* The specified zone does not exist on the server */ 
    CKErrorLimitExceeded     = 27, /* The request to the server was too large. Retry this request as a smaller batch. */ 
    CKErrorUserDeletedZone    = 28, /* The user deleted this zone through the settings UI. Your client should either remove its local data or prompt the user before attempting to re-upload any data to this zone. */ 
    CKErrorTooManyParticipants   NS_AVAILABLE(10_12, 10_0) = 29, /* A share cannot be saved because there are too many participants attached to the share */ 
    CKErrorAlreadyShared     NS_AVAILABLE(10_12, 10_0) = 30, /* A record/share cannot be saved, doing so would cause a hierarchy of records to exist in multiple shares */ 
    CKErrorReferenceViolation    NS_AVAILABLE(10_12, 10_0) = 31, /* The target of a record's parent or share reference was not found */ 
    CKErrorManagedAccountRestricted  NS_AVAILABLE(10_12, 10_0) = 32, /* Request was rejected due to a managed account restriction */ 
    CKErrorParticipantMayNeedVerification NS_AVAILABLE(10_12, 10_0) = 33, /* Share Metadata cannot be determined, because the user is not a member of the share. There are invited participants on the share with email addresses or phone numbers not associated with any iCloud account. The user may be able to join the share if they can associate one of those email addresses or phone numbers with their iCloud account via the system Share Accept UI. Call UIApplication's openURL on this share URL to have the user attempt to verify their information. */ 
} NS_ENUM_AVAILABLE(10_10, 8_0); 

Einen Ansatz, während Sie entwickeln und zu testen Sie die App, ist jede Operation Wolkenjungen für einen Fehler zu überprüfen und, wenn erkannt wird, feuert eine NSAsssert, um die App zu stoppen. Untersuchen Sie dann den Fehler, die zugrunde liegenden Fehler und den Kontext, um festzustellen, warum der Fehler aufgetreten ist und was Sie dagegen tun müssen. Wahrscheinlich werden im Laufe der Zeit allgemeine Muster auftreten, und Sie können dann überlegen, einen generischen Fehlerhandler zu erstellen.