Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
My attempt at extracting/reconstructing NSManagedObjects from dictionaries recursively in Swift. With the eventual goal of serializing dictionaries to data somewhere.
//
// MNGManagedObjectExtensions.swift
// rtj
//
// Created by Tommie N. Carter, Jr., MBA on 1/1/19.
// Copyright © 2019 MING Technology. All rights reserved.
//
import UIKit
protocol PropertyStoring {
associatedtype T
func getAssociatedObject(_ key: UnsafeRawPointer!, defaultValue: T) -> T
}
extension PropertyStoring {
func getAssociatedObject(_ key: UnsafeRawPointer!, defaultValue: T) -> T {
guard let value = objc_getAssociatedObject(self, key) as? T else {
return defaultValue
}
return value
}
}
protocol Reconstructable where Self:NSManagedObject{
func populate(fromDictionary:[AnyHashable:Any])
static func createManagedObject(fromDictionary dict:[AnyHashable:Any], inContext context:NSManagedObjectContext) -> NSManagedObject
}
protocol Extractable where Self:NSManagedObject {
func toDictionary()->[AnyHashable:Any]
}
enum TraversedState {
case yes
case no
}
extension NSManagedObject:Reconstructable, Extractable, PropertyStoring {
typealias T = TraversedState
private struct CustomProperties {
static var traversedState = TraversedState.no
}
var traversedState: TraversedState {
get {
return getAssociatedObject(&CustomProperties.traversedState, defaultValue: CustomProperties.traversedState)
}
set {
return objc_setAssociatedObject(self, &CustomProperties.traversedState, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func toDictionary() -> [AnyHashable : Any] {
self.traversedState = .yes
let attributes = self.entity.attributesByName.keys
let relationships = self.entity.relationshipsByName.keys
var dict = Dictionary<AnyHashable,Any>(minimumCapacity: (attributes.count + relationships.count + 1))
dict["class"] = self.entity.name!.components(separatedBy: ".").last! as String
attributes.forEach { (attribute) in
if let value = self.value(forKey: attribute) as? NSObject {
dict.updateValue(value, forKey: attribute)
}
}
relationships.forEach { (relationship) in
if let value = self.value(forKey: relationship) as? NSObject {
if value.isKind(of: NSSet.self) {
//to-many relationship
// The core data set holds a collection of managed objects
if let relatedObjects = value as? Set<NSManagedObject> {
// Our set holds a collection of dictionaries
let capacity = (relatedObjects).count
var dictSet = Set<[AnyHashable:AnyHashable]>(minimumCapacity: capacity)
relatedObjects.forEach({ (relatedObject) in
if relatedObject.traversedState == .no {
dictSet.insert(relatedObject.toDictionary() as! [AnyHashable : AnyHashable])
}
dict.updateValue(dictSet, forKey: relationship)
})
}
} else if value.isKind(of: NSManagedObject.self){
//to-one relationship
if let relatedObject = value as? NSManagedObject {
if relatedObject.traversedState == .no {
// Call toDictionary on the referenced object and put the result back into our dictionary.
dict.updateValue(relatedObject.toDictionary(), forKey: relationship)
}
}
}
}
}
return dict
}
func populate(fromDictionary dict: [AnyHashable : Any]) {
guard let context = self.managedObjectContext else { return }
for key in dict.keys {
if let keyValue = key as? String {
if keyValue == "class" { continue }
if let value = dict[keyValue] as? NSObject {
if value.isKind(of: NSDictionary.self) || value.isKind(of: Dictionary<AnyHashable, Any>.self as! AnyClass) {
//to-one relationship
let relatedObject = NSManagedObject.createManagedObject(fromDictionary: value as! Dictionary, inContext: context)
self.setValue(relatedObject, forKey: keyValue)
} else if value.isKind(of: Set<NSManagedObject>.self as! AnyClass) {
//to-many relationship
let relatedObjectDictionaries = value as! Set<[AnyHashable:AnyHashable]>
// Get a proxy set that represents the relationship, and add related objects to it.
// (Note: this is provided by Core Data)
let relatedObjects = self.mutableSetValue(forKey: keyValue)
for relatedObjectDictionary in relatedObjectDictionaries {
let relatedObject = NSManagedObject.createManagedObject(fromDictionary: relatedObjectDictionary, inContext: self.managedObjectContext!)
relatedObjects.add(relatedObject)
}
} else if let nonNilValue = dict[keyValue] {
//this is an attribute
self.setValue(nonNilValue, forKey: keyValue)
}
}
}
}
}
static func createManagedObject(fromDictionary dict: [AnyHashable : Any], inContext context: NSManagedObjectContext) -> NSManagedObject {
guard let className = dict["class"] as? String else { fatalError("Unable to get class name from Dictionary") }
guard let entity = NSEntityDescription.entity(forEntityName: className, in: context) else { fatalError("Unable to create EntityDescription from Dictionary") }
let newObject = NSManagedObject(entity: entity, insertInto: context)
newObject.populate(fromDictionary:dict)
return newObject
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.