This repository was archived by the owner on Dec 17, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathFetchAndSaveOperation.swift
More file actions
executable file
·120 lines (93 loc) · 4.35 KB
/
FetchAndSaveOperation.swift
File metadata and controls
executable file
·120 lines (93 loc) · 4.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//
// FetchAndSaveOperation.swift
// CloudCore
//
// Created by Vasily Ulianov on 13/03/2017.
// Copyright © 2017 Vasily Ulianov. All rights reserved.
//
import CloudKit
import CoreData
/// An operation that fetches data from CloudKit and saves it to Core Data, you can use it without calling `CloudCore.fetchAndSave` methods if you application relies on `Operation`
public class FetchAndSaveOperation: Operation {
/// Private cloud database for the CKContainer specified by CloudCoreConfig
public static let allDatabases = [
// CloudCore.config.container.publicCloudDatabase,
CloudCore.config.container.privateCloudDatabase
// CloudCore.config.container.sharedCloudDatabase
]
public typealias NotificationUserInfo = [AnyHashable : Any]
private let tokens: Tokens
private let databases: [CKDatabase]
private let persistentContainer: NSPersistentContainer
/// Called every time if error occurs
public var errorBlock: ErrorBlock?
private let queue = OperationQueue()
/// Initialize operation, it's recommended to set `errorBlock`
///
/// - Parameters:
/// - databases: list of databases to fetch data from (only private is supported now)
/// - persistentContainer: `NSPersistentContainer` that will be used to save data
/// - tokens: previously saved `Tokens`, you can generate new ones if you want to fetch all data
public init(from databases: [CKDatabase] = FetchAndSaveOperation.allDatabases, persistentContainer: NSPersistentContainer, tokens: Tokens = CloudCore.tokens) {
self.tokens = tokens
self.databases = databases
self.persistentContainer = persistentContainer
queue.name = "FetchAndSaveQueue"
}
/// Performs the receiver’s non-concurrent task.
override public func main() {
if self.isCancelled { return }
CloudCore.delegate?.willSyncFromCloud()
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundContext.name = CloudCore.config.contextName
for database in self.databases {
self.addRecordZoneChangesOperation(recordZoneIDs: [CloudCore.config.zoneID], database: database, context: backgroundContext)
}
self.queue.waitUntilAllOperationsAreFinished()
do {
try backgroundContext.save()
} catch {
errorBlock?(error)
}
CloudCore.delegate?.didSyncFromCloud()
}
private func addRecordZoneChangesOperation(recordZoneIDs: [CKRecordZone.ID], database: CKDatabase, context: NSManagedObjectContext) {
if recordZoneIDs.isEmpty { return }
let recordZoneChangesOperation = FetchRecordZoneChangesOperation(from: database, recordZoneIDs: recordZoneIDs, tokens: tokens)
recordZoneChangesOperation.recordChangedBlock = {
// Convert and write CKRecord To NSManagedObject Operation
let convertOperation = RecordToCoreDataOperation(parentContext: context, record: $0)
convertOperation.errorBlock = { self.errorBlock?($0) }
self.queue.addOperation(convertOperation)
}
recordZoneChangesOperation.recordWithIDWasDeletedBlock = {
// Delete NSManagedObject with specified recordID Operation
let deleteOperation = DeleteFromCoreDataOperation(parentContext: context, recordID: $0)
deleteOperation.errorBlock = { self.errorBlock?($0) }
self.queue.addOperation(deleteOperation)
}
recordZoneChangesOperation.errorBlock = { recordZoneID, error in
self.handle(recordZoneChangesError: error, in: recordZoneID, database: database, context: context)
}
queue.addOperation(recordZoneChangesOperation)
}
private func handle(recordZoneChangesError: Error, in recordZoneID: CKRecordZone.ID, database: CKDatabase, context: NSManagedObjectContext) {
guard let cloudError = recordZoneChangesError as? CKError else {
errorBlock?(recordZoneChangesError)
return
}
switch cloudError.code {
// User purged cloud database, we need to delete local cache (according Apple Guidelines)
case .userDeletedZone:
queue.cancelAllOperations()
let purgeOperation = PurgeLocalDatabaseOperation(parentContext: context, managedObjectModel: persistentContainer.managedObjectModel)
purgeOperation.errorBlock = errorBlock
queue.addOperation(purgeOperation)
// Our token is expired, we need to refetch everything again
case .changeTokenExpired:
tokens.tokensByRecordZoneID[recordZoneID] = nil
self.addRecordZoneChangesOperation(recordZoneIDs: [CloudCore.config.zoneID], database: database, context: context)
default: errorBlock?(cloudError)
}
}
}