refactored and fixed async for core data

This commit is contained in:
Peter 2015-10-29 18:09:40 -04:00
parent ebf4dd5bb2
commit e509c24f59
4 changed files with 106 additions and 62 deletions

View File

@ -31,6 +31,7 @@
D0CF8A0E1BE2AC1700EC9F12 /* MeteorCoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CF8A0A1BE2AC1700EC9F12 /* MeteorCoreDataStack.swift */; settings = {ASSET_TAGS = (); }; };
D0CF8A0F1BE2AC1700EC9F12 /* MeteorCoreDataCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CF8A0B1BE2AC1700EC9F12 /* MeteorCoreDataCollection.swift */; settings = {ASSET_TAGS = (); }; };
D0CF8A101BE2AC1700EC9F12 /* CoreDataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CF8A0C1BE2AC1700EC9F12 /* CoreDataExtensions.swift */; settings = {ASSET_TAGS = (); }; };
D0CF8A201BE2CA1800EC9F12 /* MeteorCoreDataTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CF8A1F1BE2CA1800EC9F12 /* MeteorCoreDataTableViewController.swift */; settings = {ASSET_TAGS = (); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -93,6 +94,7 @@
D0CF8A0A1BE2AC1700EC9F12 /* MeteorCoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeteorCoreDataStack.swift; sourceTree = "<group>"; };
D0CF8A0B1BE2AC1700EC9F12 /* MeteorCoreDataCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeteorCoreDataCollection.swift; sourceTree = "<group>"; };
D0CF8A0C1BE2AC1700EC9F12 /* CoreDataExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataExtensions.swift; sourceTree = "<group>"; };
D0CF8A1F1BE2CA1800EC9F12 /* MeteorCoreDataTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeteorCoreDataTableViewController.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -190,6 +192,7 @@
D0CF8A091BE2AC1700EC9F12 /* MeteorCoreData.swift */,
D0CF8A0A1BE2AC1700EC9F12 /* MeteorCoreDataStack.swift */,
D0CF8A0B1BE2AC1700EC9F12 /* MeteorCoreDataCollection.swift */,
D0CF8A1F1BE2CA1800EC9F12 /* MeteorCoreDataTableViewController.swift */,
D0CF8A0C1BE2AC1700EC9F12 /* CoreDataExtensions.swift */,
);
name = MeteorCoreData;
@ -360,6 +363,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D0CF8A201BE2CA1800EC9F12 /* MeteorCoreDataTableViewController.swift in Sources */,
D0CF8A0F1BE2AC1700EC9F12 /* MeteorCoreDataCollection.swift in Sources */,
D0CF8A101BE2AC1700EC9F12 /* CoreDataExtensions.swift in Sources */,
D02A72101BBEFCD300940C17 /* DDPClient.swift in Sources */,

View File

@ -50,41 +50,28 @@ func ==(lhs:MeteorCollectionChange, rhs:MeteorCollectionChange) -> Bool {
public class MeteorCoreDataCollection:Collection {
var entityName:String!
public var delegate:MeteorCoreDataCollectionDelegate?
private var entityName:String!
private let stack = MeteorCoreData.stack
private let backgroundQueue:NSOperationQueue = {
let queue = NSOperationQueue()
queue.name = "MeteorCoreData background queue"
// queue.maxConcurrentOperationCount = 1
return queue
}()
private let mainQueue = NSOperationQueue.mainQueue()
private var changeLog = [Int:MeteorCollectionChange]()
private var mainContext:NSManagedObjectContext {
return stack.mainContext
}
private var backgroundContext:NSManagedObjectContext {
return stack.backgroundContext
}
public var delegate:MeteorCoreDataCollectionDelegate?
public init(collectionName:String, entityName:String) {
super.init(name: collectionName)
self.entityName = entityName
print("Initializing Meteor Core Data Collection \(self.entityName)")
}
deinit {
}
public var managedObjectContext:NSManagedObjectContext {
return stack.managedObjectContext
}
private func newObject() -> NSManagedObject {
let entity = NSEntityDescription.entityForName(entityName, inManagedObjectContext: managedObjectContext)
let object = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedObjectContext)
return object
}
private func getObjectOnCurrentQueue(objectId:NSManagedObjectID) -> NSManagedObject? {
do {
let currentQueueObject = try self.managedObjectContext.existingObjectWithID(objectId)
@ -95,6 +82,16 @@ public class MeteorCoreDataCollection:Collection {
}
}
public var managedObjectContext:NSManagedObjectContext {
return stack.managedObjectContext
}
public func newObject() -> NSManagedObject {
let entity = NSEntityDescription.entityForName(entityName, inManagedObjectContext: managedObjectContext)
let object = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedObjectContext)
return object
}
// Retrieves all results for a given entity name
public func find() -> [NSManagedObject] {
let fetchRequest = NSFetchRequest(entityName: entityName)
@ -148,7 +145,7 @@ public class MeteorCoreDataCollection:Collection {
//
public func insert(fields:NSDictionary) {
backgroundQueue.addOperationWithBlock() {
backgroundContext.performBlock() {
let object = self.newObject()
if let id = fields.objectForKey("_id") {
@ -179,22 +176,24 @@ public class MeteorCoreDataCollection:Collection {
//
public func update(id:String, fields:NSDictionary, local:Bool) {
backgroundQueue.addOperationWithBlock() {
backgroundContext.performBlock() {
if let object = self.findOne(id) {
if let document = self.findOne(id) {
let cache = document.dictionary
self.managedObjectContext.undoManager?.beginUndoGrouping()
let change = MeteorCollectionChange(id: id, collection: self.name, fields: fields, cleared: nil)
self.changeLog[change.hashValue] = change
self.delegate?.document(willBeUpdatedWith: fields, cleared: nil, forObject: object)
self.delegate?.document(willBeUpdatedWith: fields, cleared: nil, forObject: document)
try! self.managedObjectContext.save()
self.managedObjectContext.undoManager?.endUndoGrouping()
if local == false {
let result = self.client.update(sync: self.name, document: [["_id":id], ["$set":fields]])
if result.error != nil {
log.debug("Update rejected. Attempting to rollback changes")
self.managedObjectContext.undoManager?.undoNestedGroup()
for (key, _) in fields {
document.setValue(cache.objectForKey(key), forKey: key as! String)
}
try! self.managedObjectContext.save()
}
}
@ -222,62 +221,74 @@ public class MeteorCoreDataCollection:Collection {
// In that case, the delete should only be processed locally, and no
// message regarding the delete should be sent to the server
public func remove(withId id:String, local:Bool) {
backgroundQueue.addOperationWithBlock() {
backgroundContext.performBlock() {
if let document = self.findOne(id) {
self.managedObjectContext.undoManager?.beginUndoGrouping()
let cache = document.dictionary
let id = document.valueForKey("id")
self.managedObjectContext.deleteObject(document)
try! self.managedObjectContext.save()
self.managedObjectContext.undoManager?.endUndoGrouping()
if local == false {
if let _ = id {
let result = self.client.remove(sync: self.name, document: NSArray(arrayLiteral: ["_id":id!]))
if result.error != nil {
self.managedObjectContext.undoManager?.undoNestedGroup()
let replacement = self.newObject()
self.delegate?.document(willBeCreatedWith: cache, forObject: replacement)
self.managedObjectContext.insertObject(replacement)
try! self.managedObjectContext.save()
}
}
}
}
}
}
override public func documentWasAdded(collection:String, id:String, fields:NSDictionary?) {
if !self.exists(collection, id:id) {
let object = self.newObject()
object.setValue(id, forKey: "id")
object.setValue(collection, forKey: "collection")
if let _ = self.delegate?.document(willBeCreatedWith: fields, forObject: object) {
try! managedObjectContext.save()
backgroundContext.performBlock() {
if !self.exists(collection, id:id) {
let object = self.newObject()
object.setValue(id, forKey: "id")
object.setValue(collection, forKey: "collection")
if let _ = self.delegate?.document(willBeCreatedWith: fields, forObject: object) {
do {
try self.managedObjectContext.save()
} catch let error {
log.error("\(error)")
}
}
} else {
log.info("Object \(collection) \(id) already exists in the database")
}
} else {
log.info("Object \(collection) \(id) already exists in the database")
}
}
override public func documentWasChanged(collection:String, id:String, fields:NSDictionary?, cleared:[String]?) {
let currentChange = MeteorCollectionChange(id: id, collection: collection, fields: fields, cleared: cleared)
if let priorChange = self.changeLog[currentChange.hashValue] where (priorChange.hashValue == currentChange.hashValue) {
self.changeLog[currentChange.hashValue] = nil
return
}
if let object = self.findOne(id) {
if let _ = self.delegate?.document(willBeUpdatedWith: fields, cleared: cleared, forObject: object) {
try! self.managedObjectContext.save()
backgroundContext.performBlock() {
let currentChange = MeteorCollectionChange(id: id, collection: collection, fields: fields, cleared: cleared)
if let priorChange = self.changeLog[currentChange.hashValue] where (priorChange.hashValue == currentChange.hashValue) {
self.changeLog[currentChange.hashValue] = nil
return
}
if let object = self.findOne(id) {
if let _ = self.delegate?.document(willBeUpdatedWith: fields, cleared: cleared, forObject: object) {
do {
try self.managedObjectContext.save()
} catch let error {
log.error("\(error)")
}
}
}
self.changeLog[currentChange.hashValue] = nil // Deregister the change
}
self.changeLog[currentChange.hashValue] = nil // Deregister the change
}
override public func documentWasRemoved(collection:String, id:String) {

View File

@ -63,7 +63,7 @@ public class MeteorCoreDataStack:NSObject {
let coordinator = self.persistentStoreCoordinator
var context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
context.persistentStoreCoordinator = coordinator
context.undoManager = NSUndoManager()
context.stalenessInterval = 0
return context
}()
@ -71,7 +71,7 @@ public class MeteorCoreDataStack:NSObject {
let coordinator = self.persistentStoreCoordinator
var context = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
context.persistentStoreCoordinator = coordinator
context.undoManager = NSUndoManager()
context.stalenessInterval = 0
return context
}()

View File

@ -0,0 +1,29 @@
import UIKit
import CoreData
public class MeteorCoreDataTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
public func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Move: print("> Move"); if indexPath!.isEqual(newIndexPath!) == false {
self.tableView.moveRowAtIndexPath(indexPath!, toIndexPath: newIndexPath!)
} else {
self.tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
}
case .Delete: print("> Delete"); self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
case .Insert: print("> Insert"); self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
case .Update: print("> Update"); self.tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
}
}
public func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
public func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
}