Skip to content

Instantly share code, notes, and snippets.

@soxjke
Created December 13, 2016 05:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save soxjke/f47675d781122abff61b13581cc35318 to your computer and use it in GitHub Desktop.
Save soxjke/f47675d781122abff61b13581cc35318 to your computer and use it in GitHub Desktop.
ManagedObjectMapper
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
}
}
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