Last active
May 24, 2017 10:39
-
-
Save vgorloff/2f84bca5917f53d7b7578f9436b4e151 to your computer and use it in GitHub Desktop.
This file contains 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
//: [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