mastodon-ios/MastodonSDK/Sources/CoreDataStack/Stack/ManagedObjectObserver.swift

128 lines
4.6 KiB
Swift

//
// ManagedObjectObserver.swift
// CoreDataStack
//
// Created by Cirno MainasuK on 2020-6-12.
// Copyright © 2020 Dimension. All rights reserved.
//
import Foundation
import CoreData
import Combine
final public class ManagedObjectObserver {
private init() { }
}
extension ManagedObjectObserver {
public static func observe(context: NSManagedObjectContext) -> AnyPublisher<Changes, Error> {
return NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange, object: context)
.tryMap { notification in
guard let notification = ManagedObjectContextObjectsDidChangeNotification(notification: notification) else {
throw Error.notManagedObjectChangeNotification
}
let changeTypes = ManagedObjectObserver.changeTypes(in: notification)
return Changes(
changeTypes: changeTypes,
changeNotification: notification
)
}
.mapError { error -> Error in
return (error as? Error) ?? .unknown(error)
}
.eraseToAnyPublisher()
}
public static func observe(object: NSManagedObject) -> AnyPublisher<Change, Error> {
guard let context = object.managedObjectContext else {
return Fail(error: .noManagedObjectContext).eraseToAnyPublisher()
}
return NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange, object: context)
.tryMap { notification in
guard let notification = ManagedObjectContextObjectsDidChangeNotification(notification: notification) else {
throw Error.notManagedObjectChangeNotification
}
let changeType = ManagedObjectObserver.changeType(of: object, in: notification)
return Change(
changeType: changeType,
changeNotification: notification
)
}
.mapError { error -> Error in
return (error as? Error) ?? .unknown(error)
}
.eraseToAnyPublisher()
}
}
extension ManagedObjectObserver {
private static func changeTypes(in notification: ManagedObjectContextObjectsDidChangeNotification) -> [ChangeType] {
var changeTypes: [ChangeType] = []
let deleted = notification.deletedObjects.union(notification.invalidedObjects)
for object in deleted {
changeTypes.append(.delete(object))
}
let updated = notification.updatedObjects.union(notification.refreshedObjects)
for object in updated {
changeTypes.append(.update(object))
}
return changeTypes
}
private static func changeType(of object: NSManagedObject, in notification: ManagedObjectContextObjectsDidChangeNotification) -> ChangeType? {
let deleted = notification.deletedObjects.union(notification.invalidedObjects)
if notification.invalidatedAllObjects || deleted.contains(where: { $0 === object }) {
return .delete(object)
}
let updated = notification.updatedObjects.union(notification.refreshedObjects)
if let object = updated.first(where: { $0 === object }) {
return .update(object)
}
return nil
}
}
extension ManagedObjectObserver {
public struct Changes {
public let changeTypes: [ChangeType]
public let changeNotification: ManagedObjectContextObjectsDidChangeNotification
init(changeTypes: [ManagedObjectObserver.ChangeType], changeNotification: ManagedObjectContextObjectsDidChangeNotification) {
self.changeTypes = changeTypes
self.changeNotification = changeNotification
}
}
public struct Change {
public let changeType: ChangeType?
public let changeNotification: ManagedObjectContextObjectsDidChangeNotification
init(changeType: ManagedObjectObserver.ChangeType?, changeNotification: ManagedObjectContextObjectsDidChangeNotification) {
self.changeType = changeType
self.changeNotification = changeNotification
}
}
public enum ChangeType {
case delete(NSManagedObject)
case update(NSManagedObject)
}
public enum Error: Swift.Error {
case unknown(Swift.Error)
case noManagedObjectContext
case notManagedObjectChangeNotification
}
}