Skip to content

Instantly share code, notes, and snippets.

@lucasecf
Created November 13, 2015 11:46
Show Gist options
  • Save lucasecf/c052014d35a854d2dbd9 to your computer and use it in GitHub Desktop.
Save lucasecf/c052014d35a854d2dbd9 to your computer and use it in GitHub Desktop.
Example of extension and subclass of NSManagedObject to use in all models
//
// 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