Last active
April 14, 2021 12:46
-
-
Save tonnylitao/00c7cb8efeebfa0e91ae333d2c3e6396 to your computer and use it in GitHub Desktop.
yet another way to sync core data with paging api
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import CoreData | |
struct RemoteUser: Decodable { | |
var userId: Int | |
var name: String | |
} | |
class User: NSManagedObject { | |
@NSManaged public var userId: Int | |
@NSManaged public var name: String | |
func importFrom(_ object: RemoteUser) { | |
//get rid of context.hasChanges is true even set same value | |
if userId != object.userId { userId = object.userId } | |
if name != object.name { name = object.name } | |
} | |
} | |
class PagingDataSyncEngine { | |
var container: NSPersistentContainer! | |
private lazy var backgroundContext: NSManagedObjectContext = { | |
container.newBackgroundContext() | |
}() | |
func sync(from remoteUsers: [RemoteUser], offset: Int, isFullFilled: Bool, completion: @escaping (Error?) -> ()) { | |
let moc = backgroundContext | |
moc.perform { | |
var err: Error? | |
do { | |
if remoteUsers.isEmpty { | |
try self.batchDeleteUsers { fetchRequest in | |
fetchRequest.fetchOffset = offset | |
} | |
}else { | |
try self.sync(from: remoteUsers, isFullFilled: isFullFilled) | |
if moc.hasChanges { | |
try moc.save() | |
} | |
} | |
}catch { | |
err = error | |
} | |
DispatchQueue.main.async { | |
completion(err) | |
} | |
} | |
} | |
private func sync(from remoteUsers: [RemoteUser], isFullFilled: Bool) throws { | |
guard !remoteUsers.isEmpty else { fatalError() } | |
let ids = remoteUsers.map { $0.userId } | |
// batch delete | |
var predicates = [NSPredicate]() | |
if !isFullFilled { | |
predicates.append(NSPredicate(format: "%K > %d", #keyPath(User.userId), ids.last!)) | |
} | |
if remoteUsers.count > 1 { | |
let (min, max) = (ids.first!, ids.last!) | |
let key = #keyPath(User.userId) | |
predicates.append(NSPredicate(format: "%K > %d AND %K < %d AND NOT %K IN %@", key, min, key, max, key, ids)) | |
} | |
if !predicates.isEmpty { | |
try batchDeleteUsers { fetchRequest in | |
fetchRequest.predicate = NSCompoundPredicate(orPredicateWithSubpredicates: predicates) | |
} | |
} | |
// find-or-create | |
let fetchRequest = User.fetchRequest() | |
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(User.userId), ascending: true)] | |
fetchRequest.predicate = NSPredicate(format: "%K IN %@", #keyPath(User.userId), ids) | |
let existedUsers = try backgroundContext.fetch(fetchRequest) as! [User] | |
let idUserMapping = existedUsers.reduce(into: [:]) { $0[$1.userId] = $1 } | |
remoteUsers.forEach { item in | |
if let user = idUserMapping[item.userId] { | |
// update | |
user.importFrom(item) | |
}else { | |
// create | |
let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: backgroundContext) as! User | |
user.importFrom(item) | |
} | |
} | |
} | |
private func batchDeleteUsers(decoration: (NSFetchRequest<NSFetchRequestResult>) -> ()) throws { | |
let fetchRequest = User.fetchRequest() | |
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(User.userId), ascending: true)] | |
decoration(fetchRequest) | |
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) | |
batchDeleteRequest.resultType = .resultTypeObjectIDs | |
let result = try backgroundContext.execute(batchDeleteRequest) as? NSBatchDeleteResult | |
if let ids = result?.result as? [NSManagedObjectID], !ids.isEmpty { | |
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: [NSDeletedObjectsKey: ids], into: [container.viewContext]) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment