Created
November 13, 2015 11:46
-
-
Save lucasecf/c052014d35a854d2dbd9 to your computer and use it in GitHub Desktop.
Example of extension and subclass of NSManagedObject to use in all models
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
// | |
// NSManagedObject+ActiveRecord.swift | |
// EventCast | |
// | |
// Created by Lucas Eduardo on 04/11/15. | |
// Copyright © 2015 RNCDev. All rights reserved. | |
// | |
import Foundation | |
import CoreData | |
import Cent | |
/** | |
***** ACTIVE RECORD Pattern like methods for models ***** | |
Using a superclass allows us to create a default init, with no args, to create a new instance regarding of saving it or not | |
*/ | |
class CoreDataModel: NSManagedObject { | |
var changed: Bool = false //tells if the element had any attribute changed during sync from server | |
@NSManaged var identifier: String | |
var context: NSManagedObjectContext { | |
return self.context | |
} | |
static var context: NSManagedObjectContext { | |
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate | |
return appDelegate.managedObjectContext | |
} | |
convenience init() { | |
let entityName = self.dynamicType.className | |
let entity = NSEntityDescription.entityForName(entityName, inManagedObjectContext: CoreDataModel.context)! | |
self.init(entity: entity, insertIntoManagedObjectContext: CoreDataModel.context) | |
} | |
} | |
/** | |
Using a protocol and it's extension allows us to create generic methods that are already typed, through the Self reserved word, to the final class that implements the protocol. In this way, we can avoid typecastings when returning anyobjects or explicit typing when using generics. | |
*/ | |
protocol CoreDataActiveRecord { | |
//init | |
init(identifier: String) | |
//Instance methods | |
func save() | |
func destroy() | |
//static methods | |
static func saveAll() | |
static func all(orderBy: String?, ascending: Bool) -> [Self] | |
static func findByIdentifier(identifier: String) -> Self? | |
static func findOrCreateByIdentifier(identifier: String) -> Self | |
//sync server methods | |
static func syncLocalDBElement(local localElement: CoreDataModel, fromServer serverElement: CoreDataModel) | |
static func syncLocalDBCollection(var local localCollection: [Self], fromServer serverCollection: [Self]) -> (syncCollection: [Self], changedElements: [Self], addedElements: [Self], removedElements: [Self]) | |
} | |
extension CoreDataActiveRecord where Self: CoreDataModel { | |
init(identifier: String) { | |
self.init() | |
self.identifier = identifier | |
} | |
/** | |
Calls the save method for the default context. Be aware that, although this is an instance method, this will save everything changed in the current context. | |
*/ | |
func save() { | |
Self.saveAll() | |
} | |
/** | |
Removes the object from database | |
*/ | |
func destroy() { | |
Self.context.deleteObject(self) | |
Self.saveAll() | |
} | |
/** | |
Calls the save method for the default context. Be aware that, although this is an instance method, this will save everything changed in the current context. | |
*/ | |
static func saveAll() { | |
try! self.context.save() | |
} | |
/** | |
Gets all instances of the model inside DB. | |
*/ | |
static func all(orderBy: String? = nil, ascending: Bool = true) -> [Self] { | |
let fetchRequest = NSFetchRequest(entityName: self.className) | |
if let orderBy = orderBy { | |
fetchRequest.sortDescriptors = [NSSortDescriptor(key: orderBy, ascending: ascending)] | |
} | |
return try! context.executeFetchRequest(fetchRequest) as! [Self] | |
} | |
/** | |
Find and return the element of the passed id. Return nil if the element does not exists | |
*/ | |
static func findByIdentifier(identifier: String) -> Self? { | |
guard identifier != "" else { return nil } | |
let fetchRequest = NSFetchRequest(entityName: self.className) | |
fetchRequest.predicate = NSPredicate(format: "%K = %@", "identifier", identifier) | |
return try! context.executeFetchRequest(fetchRequest).first() as? Self | |
} | |
/** | |
Try to dind and return the element of the passed id. if such element does not exist, create a new one with the passed id. | |
*/ | |
static func findOrCreateByIdentifier(identifier: String) -> Self { | |
if let element = Self.findByIdentifier(identifier) { | |
return element | |
} | |
return Self.init(identifier: identifier) | |
} | |
/** | |
This synchronizes a single element in memory, loaded originally from database, with a equivalent element freshly loaded from server. | |
*/ | |
static func syncLocalDBElement(local localElement: CoreDataModel, fromServer serverElement: CoreDataModel) { | |
for attrName in localElement.entity.attributesByName.keys { | |
let oldValue = localElement.valueForKey(attrName) as? NSObject | |
let newValue = serverElement.valueForKey(attrName) as? NSObject | |
if let oldValue = oldValue, newValue = newValue where oldValue != newValue { | |
localElement.changed = true | |
localElement.setValue(newValue, forKey: attrName) | |
} | |
} | |
} | |
/** | |
This synchronizes a local array in memory, loaded originally from database, with a new array freshly loaded from server. | |
*/ | |
static func syncLocalDBCollection(var local localCollection: [Self], var fromServer serverCollection: [Self]) -> (syncCollection: [Self], changedElements: [Self], addedElements: [Self], removedElements: [Self]) { | |
var changedElements = [Self]() | |
var addedElements = [Self]() | |
var removedElements = [Self]() | |
//*** Update or remove elements *** | |
for localElement in localCollection { | |
let serverElement = serverCollection.filter{ $0.identifier == localElement.identifier }.first() | |
if let serverElement = serverElement { | |
//if element exists in server array, just sync its properties. Then, we need to remove the 'copy' from coredata | |
self.syncLocalDBElement(local: localElement, fromServer: serverElement) | |
if localElement.changed { | |
changedElements.append(localElement) | |
} | |
serverCollection.remove(serverElement) | |
serverElement.destroy() | |
} else { | |
//delete element locally if it's not in server array anymore | |
localCollection.remove(localElement) | |
removedElements.append(localElement) | |
} | |
} | |
//*** Add new elements *** | |
for serverElement in serverCollection { | |
let localElement = localCollection.filter{ $0.identifier == serverElement.identifier }.first() | |
//if server element does not exist locally, add it in the array | |
if localElement == nil { | |
localCollection.append(serverElement) | |
addedElements.append(serverElement) | |
} | |
} | |
return (syncCollection: localCollection, changedElements: changedElements, addedElements: addedElements, removedElements: removedElements) | |
} | |
} | |
/** | |
Used to get the class name as a String, to be used for the NSEntityDescription | |
*/ | |
extension NSObject { | |
public class var className: String { | |
return NSStringFromClass(self).componentsSeparatedByString(".").last! | |
} | |
public var className: String { | |
return NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last! | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment