Created
December 13, 2016 05:34
-
-
Save soxjke/f47675d781122abff61b13581cc35318 to your computer and use it in GitHub Desktop.
ManagedObjectMapper
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
import CoreData | |
public class CoreDataStack { | |
// 2 | |
public let model:NSManagedObjectModel | |
public let persistentStoreCoordinator:NSPersistentStoreCoordinator | |
public let context:NSManagedObjectContext | |
public init() { | |
// 3.1 | |
// initialize our MO1 entity | |
let MO1Entity = NSEntityDescription() | |
MO1Entity.name = "MO1" | |
MO1Entity.managedObjectClassName = "MO1" | |
do { | |
// create the attributes for the entity | |
let prop1Attribute = NSAttributeDescription() | |
prop1Attribute.name = "prop1" | |
prop1Attribute.attributeType = .stringAttributeType | |
prop1Attribute.isOptional = false | |
prop1Attribute.isIndexed = false | |
let prop2Attribute = NSAttributeDescription() | |
prop2Attribute.name = "prop2" | |
prop2Attribute.attributeType = .integer64AttributeType | |
prop2Attribute.isOptional = false | |
prop2Attribute.isIndexed = false | |
// add the properties to the entity | |
MO1Entity.properties = [prop1Attribute, prop2Attribute] | |
} | |
// 3.2 | |
// initialize our MO2 entity | |
let MO2Entity = NSEntityDescription() | |
MO2Entity.name = "MO2" | |
MO2Entity.managedObjectClassName = "MO2" | |
do { | |
// create the attributes for the entity | |
let prop1Attribute = NSAttributeDescription() | |
prop1Attribute.name = "prop1" | |
prop1Attribute.attributeType = .dateAttributeType | |
prop1Attribute.isOptional = false | |
prop1Attribute.isIndexed = false | |
let rel1Attribute = NSRelationshipDescription() | |
rel1Attribute.name = "rel1" | |
rel1Attribute.destinationEntity = MO1Entity | |
rel1Attribute.minCount = 1 | |
rel1Attribute.maxCount = 1 | |
rel1Attribute.isOptional = false | |
rel1Attribute.isIndexed = false | |
let rel2Attribute = NSRelationshipDescription() | |
rel2Attribute.name = "rel2" | |
rel2Attribute.destinationEntity = MO1Entity | |
rel2Attribute.minCount = 0 | |
rel2Attribute.maxCount = 0 | |
rel2Attribute.isOrdered = false | |
rel2Attribute.isOptional = false | |
rel2Attribute.isIndexed = false | |
// add the properties to the entity | |
MO2Entity.properties = [prop1Attribute, rel1Attribute, rel2Attribute] | |
} | |
// add the entities to the model | |
model = NSManagedObjectModel() | |
model.entities = [MO1Entity, MO2Entity] | |
// 4 | |
// setup the persistent store coordinator | |
persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model) | |
_ = try! persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil) | |
// 5 | |
// set up managed object context | |
context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) | |
context.persistentStoreCoordinator = persistentStoreCoordinator | |
} | |
} |
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
import CoreData | |
protocol ManagedObjectMappableDescription { | |
func mapping() -> [String:String] | |
} | |
protocol KeyValueMappable { | |
func valueForKey<T>(key: String) -> T? | |
func valueForKey<T>(key: String, dumbParameterBecauseSwiftSucks: T?) -> T? | |
} | |
protocol KeyValueMappableInitializable { | |
init(mappable: KeyValueMappable?) | |
} | |
protocol MOConvertable : ManagedObjectMappableDescription { | |
func toManagedObject<ManagedObjectClass: NSManagedObject>(context: NSManagedObjectContext, dynamicTypeName: String?) -> ManagedObjectClass | |
} | |
protocol DTOConvertable { | |
func toDTO<DTOClass: DTO>() -> DTOClass | |
} | |
extension NSManagedObject : DTOConvertable { | |
func toDTO<DTOClass : DTO>() -> DTOClass { | |
let dto = DTOClass(mappable: self) | |
return dto | |
} | |
} | |
extension NSManagedObject : KeyValueMappable { | |
func valueForKey<T>(key: String) -> T? { | |
return value(forKey: key) as? T | |
} | |
func valueForKey<T>(key: String, dumbParameterBecauseSwiftSucks: T? = nil) -> T? { | |
return value(forKey: key) as? T | |
} | |
} | |
extension NSEntityDescription { | |
func relationshipForName(_ name: String) -> NSRelationshipDescription? { | |
return relationshipsByName.filter({ (keyInEntityDescription, _) -> Bool in | |
return keyInEntityDescription == name | |
}).first?.value | |
} | |
func attributeForName(_ name: String) -> NSAttributeDescription? { | |
return attributesByName.filter({ (keyInEntityDescription, _) -> Bool in | |
return keyInEntityDescription == name | |
}).first?.value | |
} | |
} | |
class DTO : MOConvertable, KeyValueMappableInitializable { | |
required init(mappable: KeyValueMappable?) { | |
guard mappable != nil else { return } | |
} | |
func toManagedObject<ManagedObjectClass : NSManagedObject>(context: NSManagedObjectContext, dynamicTypeName: String? = nil) -> ManagedObjectClass { | |
let entityName = dynamicTypeName ?? String(describing: ManagedObjectClass.self) | |
let entity: NSEntityDescription = NSEntityDescription.entity(forEntityName: entityName, in: context)! | |
let mo: ManagedObjectClass = ManagedObjectClass(entity: entity, insertInto: context) | |
let mirror = Mirror(reflecting: self) | |
mapping().forEach { (key: String, keyInMo: String) in | |
if let value = valueForKey(key: key, inMirror: mirror) { | |
setValue(value, inManagedObject: mo, forKey: keyInMo, withEntity: entity) | |
} | |
} | |
return mo | |
} | |
private func valueForKey(key: String, inMirror: Mirror) -> Any? { | |
return inMirror.children.filter({ $0.label == key }).first?.value | |
} | |
private func setValue(_ value: Any?, inManagedObject mo: NSManagedObject, forKey key: String, withEntity entity: NSEntityDescription) { | |
if let valueToOneRelation = value as? DTO { | |
setToOneValue(valueToOneRelation, inManagedObject: mo, forKey: key, withEntity: entity) | |
} | |
else if let valueToManyRelation = value as? [DTO] { | |
setToManyValue(valueToManyRelation, inManagedObject: mo, forKey: key, withEntity: entity) | |
} | |
else { | |
setScalarValue(value, inManagedObject: mo, forKey: key, withEntity: entity) | |
} | |
} | |
private func setScalarValue(_ value: Any?, inManagedObject mo: NSManagedObject, forKey key: String, withEntity entity: NSEntityDescription) { | |
if let attributeDescription = entity.attributeForName(key) { | |
mo.setValue(value, forKey: attributeDescription.name) | |
} | |
} | |
private func setToOneValue(_ value: DTO, inManagedObject mo: NSManagedObject, forKey key: String, withEntity entity: NSEntityDescription) { | |
if let relationshipDescription = entity.relationshipForName(key) { | |
mo.setValue(value.toManagedObject(context: mo.managedObjectContext!, dynamicTypeName: relationshipDescription.destinationEntity?.name), forKey: key) | |
} | |
} | |
private func setToManyValue(_ value: [DTO], inManagedObject mo: NSManagedObject, forKey key: String, withEntity entity: NSEntityDescription) { | |
if let relationshipDescription = entity.relationshipForName(key) { | |
let moArray = moArrayFromDTOArray(value, inContext: mo.managedObjectContext!, entityName: relationshipDescription.destinationEntity?.name) | |
let moCollection : Any = relationshipDescription.isOrdered ? NSOrderedSet(array: moArray) : NSSet(array: moArray) | |
mo.setValue(moCollection, forKey: key) | |
} | |
} | |
private func moArrayFromDTOArray(_ value: [DTO], inContext context: NSManagedObjectContext, entityName: String?) -> [NSManagedObject] { | |
return value.map { $0.toManagedObject(context: context, dynamicTypeName: entityName) } | |
} | |
convenience init() { | |
self.init(mappable : nil) | |
} | |
func mapping() -> [String : String] { | |
return [:] | |
} | |
} | |
@objc(MO1) | |
class MO1 : NSManagedObject { | |
@NSManaged var prop1 : String | |
@NSManaged var prop2 : NSNumber | |
} | |
@objc(MO2) | |
class MO2 : NSManagedObject { | |
@NSManaged var prop1 : Date | |
@NSManaged var rel1 : MO1 | |
@NSManaged var rel2 : Set<MO1> | |
} | |
class MO1DTO : DTO { | |
var prop1 : String? | |
var prop2 : Int? | |
required init(mappable: KeyValueMappable?) { | |
super.init(mappable: mappable) | |
guard let mappable = mappable else { return } | |
self.prop1 = mappable.valueForKey(key: "prop1") | |
self.prop2 = mappable.valueForKey(key: "prop2") | |
} | |
override func mapping() -> [String : String] { | |
return ["prop1":"prop1", "prop2":"prop2"] | |
} | |
} | |
extension MO1DTO : Equatable { | |
public static func == (lhs: MO1DTO, rhs: MO1DTO) -> Bool { | |
return (lhs.prop1 == rhs.prop1) && (lhs.prop2 == rhs.prop2) | |
} | |
} | |
extension MO1DTO : Hashable { | |
public var hashValue: Int { | |
get { | |
return (prop1?.hashValue ?? 0) + (prop2?.hashValue ?? 0) | |
} | |
} | |
} | |
class MO2DTO : DTO { | |
var prop1 : Date? | |
var rel1 : MO1DTO? | |
var rel2 : [MO1DTO]? | |
required init(mappable: KeyValueMappable?) { | |
super.init(mappable: mappable) | |
guard let mappable = mappable else { return } | |
self.prop1 = mappable.valueForKey(key: "prop1") | |
self.rel1 = MO1DTO(mappable: mappable.valueForKey(key: "rel1")) | |
self.rel2 = mappable.valueForKey(key: "rel2", dumbParameterBecauseSwiftSucks: (nil as Set<NSManagedObject>?))?.map({ MO1DTO(mappable: $0) }) | |
} | |
override func mapping() -> [String : String] { | |
return ["prop1":"prop1", "rel1":"rel1", "rel2":"rel2"] | |
} | |
} | |
// Stack setup | |
let coreDataStack : CoreDataStack = CoreDataStack() | |
let date = Date() | |
let dto1 = MO1DTO() | |
dto1.prop1 = "Hello from DTO" | |
dto1.prop2 = 2016 | |
let dto2 = MO2DTO() | |
dto2.prop1 = date | |
dto2.rel1 = dto1 | |
dto2.rel2 = [dto1] | |
let MO1entity: NSEntityDescription = NSEntityDescription.entity(forEntityName: String(describing: MO1.self), in: coreDataStack.context)! | |
let MO2entity: NSEntityDescription = NSEntityDescription.entity(forEntityName: String(describing: MO2.self), in: coreDataStack.context)! | |
let mo1 = MO1(entity: MO1entity, insertInto: coreDataStack.context) | |
mo1.prop1 = "Hello" | |
mo1.prop2 = 2016 | |
let mo2 = MO2(entity: MO2entity, insertInto: coreDataStack.context) | |
mo2.prop1 = date | |
mo2.rel1 = mo1 | |
mo2.rel2 = [mo1] | |
// To NSManagedObject | |
let moFromDto1 : MO1 = dto1.toManagedObject(context: coreDataStack.context) | |
let moFromDto2 : MO2 = dto2.toManagedObject(context: coreDataStack.context) | |
assert(moFromDto1.prop1 == dto1.prop1) | |
assert(Int(moFromDto1.prop2) == dto1.prop2) | |
assert(moFromDto2.prop1 == dto2.prop1) | |
assert(moFromDto2.rel1.prop1 == moFromDto1.prop1) | |
assert(moFromDto2.rel1.prop2 == moFromDto1.prop2) | |
assert(moFromDto2.rel2.first?.prop1 == moFromDto1.prop1) | |
assert(moFromDto2.rel2.first?.prop2 == moFromDto1.prop2) | |
// To DTO | |
let dtoFromMo1 : MO1DTO = mo1.toDTO() | |
let dtoFromMo2 : MO2DTO = mo2.toDTO() | |
assert(dtoFromMo1.prop1 == mo1.prop1) | |
assert(dtoFromMo1.prop2 == Int(mo1.prop2)) | |
assert(dtoFromMo2.prop1 == mo2.prop1) | |
assert(dtoFromMo2.rel1 == dtoFromMo1) | |
assert(dtoFromMo2.rel2?.first?.prop1 == dtoFromMo1.prop1) | |
assert(dtoFromMo2.rel2?.first?.prop2 == dtoFromMo1.prop2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment