Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save vgorloff/2f84bca5917f53d7b7578f9436b4e151 to your computer and use it in GitHub Desktop.
Save vgorloff/2f84bca5917f53d7b7578f9436b4e151 to your computer and use it in GitHub Desktop.
//: [Previous](@previous)
import PlaygroundSupport
import Cocoa
import CoreData
PlaygroundPage.current.needsIndefiniteExecution = true
extension NSManagedObject {
public static var entityName: String {
let className = String(describing: self)
return className.components(separatedBy: ".").last!
}
public convenience init(in context: NSManagedObjectContext) throws {
let entityName = type(of: self).entityName
guard let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: context) else {
fatalError()
}
self.init(entity: entityDescription, insertInto: context)
}
}
@objc(UserInfoEntity)
class UserInfoEntity: NSManagedObject {
@NSManaged var id: Int64
@NSManaged var name: String
convenience init(id: Int64, name: String, in context: NSManagedObjectContext) throws {
try self.init(in: context)
self.id = id
self.name = name
}
}
class DBStack {
static let shared = DBStack()
static var mainContext: NSManagedObjectContext {
return shared.mainContext
}
private typealias PSC = NSPersistentStoreCoordinator
private lazy var coordinator: PSC = PSC(managedObjectModel: self.model)
private lazy var model: NSManagedObjectModel = self.setupModel()
private lazy var writerContext: NSManagedObjectContext = self.setupWriterContext()
private lazy var mainContext: NSManagedObjectContext = self.setupMainContext()
private var isInitialized = false
init() {
}
func setupInMemoryStore() throws {
guard !isInitialized else { return }
isInitialized = true
try coordinator.addPersistentStore(ofType: NSInMemoryStoreType,
configurationName: nil, at: nil, options: nil)
}
static func makeChildContext() -> NSManagedObjectContext {
let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
moc.parent = mainContext
return moc
}
private func setupWriterContext() -> NSManagedObjectContext {
let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
moc.persistentStoreCoordinator = coordinator
return moc
}
private func setupMainContext() -> NSManagedObjectContext {
let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
moc.parent = writerContext
return moc
}
private func setupModel() -> NSManagedObjectModel {
let attributeID = NSAttributeDescription()
attributeID.name = #keyPath(UserInfoEntity.id)
attributeID.attributeType = .integer64AttributeType
attributeID.isOptional = false
attributeID.isIndexed = true
let attributeName = NSAttributeDescription()
attributeName.name = #keyPath(UserInfoEntity.name)
attributeName.attributeType = .stringAttributeType
attributeName.isOptional = false
let entityUserInfo = NSEntityDescription()
entityUserInfo.name = UserInfoEntity.entityName
entityUserInfo.managedObjectClassName = UserInfoEntity.entityName
entityUserInfo.properties = [attributeID, attributeName]
let model = NSManagedObjectModel()
model.entities = [entityUserInfo]
return model
}
}
public struct ManagedObjectContextObserverChangeInfo<T> {
public let inserted: [T]
public let updated: [T]
public let deleted: [T]
public init(inserted: [T], updated: [T], deleted: [T]) {
self.inserted = inserted
self.updated = updated
self.deleted = deleted
}
public var isEmpty: Bool {
return inserted.isEmpty && updated.isEmpty && deleted.isEmpty
}
}
public class ManagedObjectContextObserver<T: NSManagedObject> {
public var predicate: NSPredicate?
public var onContextChanged: ((ManagedObjectContextObserverChangeInfo<T>) -> Void)?
private let notificationName = Notification.Name.NSManagedObjectContextDidSave
private var observer: NSObjectProtocol!
private weak var context: NSManagedObjectContext?
public init(context: NSManagedObjectContext) {
let nc = NotificationCenter.default
observer = nc.addObserver(forName: notificationName, object: context, queue: nil) { [weak self] note in
let insertedAll = ((note.userInfo?[NSInsertedObjectsKey] as? NSSet)?.allObjects as? [NSManagedObject]) ?? []
let updatedAll = ((note.userInfo?[NSUpdatedObjectsKey] as? NSSet)?.allObjects as? [NSManagedObject]) ?? []
let deletedAll = ((note.userInfo?[NSDeletedObjectsKey] as? NSSet)?.allObjects as? [NSManagedObject]) ?? []
var inserted = ((insertedAll.filter { $0 is T }) as? [T]) ?? []
var updated = ((updatedAll.filter { $0 is T }) as? [T]) ?? []
var deleted = ((deletedAll.filter { $0 is T }) as? [T]) ?? []
if let predicate = self?.predicate {
inserted = inserted.filter { predicate.evaluate(with: $0) }
updated = updated.filter { predicate.evaluate(with: $0) }
deleted = deleted.filter { predicate.evaluate(with: $0) }
}
let info = ManagedObjectContextObserverChangeInfo(inserted: inserted, updated: updated, deleted: deleted)
if !info.isEmpty {
self?.onContextChanged?(info)
}
}
self.context = context
}
deinit {
NotificationCenter.default.removeObserver(observer, name: notificationName, object: context)
}
}
func updateUserInfo(id: Int64, name: String) {
let privateContext = DBStack.makeChildContext()
privateContext.perform {
let request: NSFetchRequest<UserInfoEntity> = NSFetchRequest(entityName: UserInfoEntity.entityName)
request.predicate = NSPredicate(format: "%K == %@", argumentArray: [#keyPath(UserInfoEntity.id), id])
request.fetchLimit = 1
do {
if let userInfo = try privateContext.fetch(request).first {
userInfo.name = name
} else {
_ = try UserInfoEntity(id: id, name: name, in: privateContext)
}
if privateContext.hasChanges {
print("→ Will save userInfo. Name: " + name)
try privateContext.save()
if let parent = privateContext.parent {
try parent.save()
}
}
} catch {
print(error)
}
}
}
let stack = DBStack()
try stack.setupInMemoryStore()
let userID: Int64 = 1
let observer = ManagedObjectContextObserver<UserInfoEntity>(context: DBStack.mainContext)
observer.predicate = NSPredicate(format: "%K == %@", argumentArray: [#keyPath(UserInfoEntity.id), userID])
observer.onContextChanged = {
if let userInfo = $0.inserted.first {
print("! UserInfo inserted: \(String(describing: userInfo.name))")
}
if let userInfo = $0.updated.first {
print("! UserInfo updated: \(String(describing: userInfo.name))")
}
if let userInfo = $0.deleted.first {
print("! UserInfo deleted: \(String(describing: userInfo.name))")
}
}
DispatchQueue.global().async {
updateUserInfo(id: userID, name: "Alex")
updateUserInfo(id: userID, name: "Alexander")
}
//: [Next](@next)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment