Swift y CoreData. O cómo construir Swift ORM basado en Objective-C ORM

¡Habr, hola! Mi nombre es Geor y estoy desarrollando proyectos de iOS en Prisma Labs. Como probablemente entendiste, hoy hablaremos de la cordata y muchos de ustedes ya se aburrieron en este momento. Pero no te apresures a desesperarte, ya que hablaremos sobre todo de la magia de Swift y del metal. Broma - sobre el metal en otro momento. La historia será sobre cómo derrotamos el texto estándar de NSManaged, reinventamos las migraciones y volvimos a hacer que la cordata fuera genial.





Desarrolladores, vamos.





Algunas palabras sobre la motivación.

Es difícil trabajar con cordata. Especialmente en nuestro rápido tiempo. Este es un marco muy antiguo que se creó como una capa de datos con énfasis en la optimización de E / S, que por defecto lo hacía más complejo que otras formas de almacenar datos. Pero la productividad del hierro a lo largo del tiempo ha dejado de ser un cuello de botella y, lamentablemente, la complejidad de la cordata no ha desaparecido. En las aplicaciones modernas, muchas personas prefieren otros marcos a cordata: Realm, GRDB (arriba), etc. O simplemente usan archivos (por qué no). Incluso Apple en los nuevos tutoriales usa serialización / deserialización codificable para la persistencia.





, (. NSPersistentContainer), - NSManaged , / , , - . NSManaged-.





- , , ( SQL) , .





, .





- Sworm.





- , .





NSManagedObject- CoreData-

NSManagedObject' key-value . , , KV- . 3 :

















- :





struct Foo {
    static let entityName: String = "FooEntity"
}
      
      



"" - . , :





Foo.entityName
      
      



, destination-, . . -, , NSManageObject , , , -, Relation<T: >(name: String), , . , - . , 1:





protocol ManagedObjectConvertible {
    static var entityName: String { get }
}
      
      



:





Relation<T: ManageObjectConvertible>(name: String)
      
      



:





struct Foo: ManageObjectConvertible {
    static var entityName: String = "FooEntity"

    static let relation1 = Relation<Bar1>(name: "bar1")
    static let relation2 = Relation<Bar2>(name: "bar2")
}
      
      



() , , ? . -, , -, , Relation - one/many/orderedmany, , . , . , . , - 2:





protocol ManagedObjectConvertible {
    associatedtype Relations

    static var entityName: String { get }
    static var relations: Relations { get }
}
      
      



, :





struct Foo: ManageObjectConvertible {
    static let entityName: String = "FooEntity"

    struct Relations {
        let relation1 = Relation<Bar1>(name: "bar1")
        let relation2 = Relation<Bar2>(name: "bar2")
    }

    static let relations = Relations()
}
      
      



- :





extension ManagedObjectConvertible {
    func relationName<T: ManagedObjectConvertible>(
        keyPath: KeyPath<Self.Relations, Relation<T>>
    ) -> String {
        Self.relations[keyPath: keyPath].name
    }
}
      
      



- , :)





-

.





, "" , , , . , : . , WritableKeyPath + String key. , , - , .





Attribute<T>, T - . `[Attribute<T>]` T Self. , - 3:





public protocol ManagedObjectConvertible {
    associatedtype Relations

    static var entityName: String { get }
    static var attributes: [Attribute<Self>] { get }
    static var relations: Relations { get }
}
      
      



Attribute. , / KV-. , :





final class Attribute<T: ManagedObjectConvertible, V> {
    let keyPath: WritableKeyPath<T, V>
    let key: String

    ...

    func update(container: NSManagedObject, model: T) {
        container.setValue(model[keyPath: keyPath], forKey: key)
    }

    func update(model: inout T, container: NSManagedObject) {
        model[keyPath: keyPath] = container.value(forKey: key) as! V
    }
}
      
      



, [Attribute<T, V>] - . V , ? , :





final class Attribute<T: ManagedObjectConvertible> {
    ...

    init<V>(
        keyPath: WritableKeyPath<T, V>,
        key: String
    ) { ... }

    ...
}
      
      



V . , , BFG - :





final class Attribute<T: ManagedObjectConvertible> {
    let encode: (T, NSManagedObject) -> Void
    let decode: (inout T, NSManagedObject) -> Void

    init<V>(keyPath: WritableKeyPath<T, V>, key: String) {
        self.encode = {
            $1.setValue($0[keyPath: keyPath], forKey: key)
        }

        self.decode = {
            $0[keyPath: keyPath] = $1.value(forKey: key) as! V
        }
    }
}
      
      



. NSManagedObject , NSManagedObject', , .





- 4, :





protocol ManagedObjectConvertible {
    associatedtype Relations

    static var entityName: String { get }
    static var attributes: Set<Attribute<Self>> { get }
    static var relations: Relations { get }

    init()
}
      
      



- NSManagedObject', .





.





- bool, int, double, string, data, etc. Transformable, . .





-:





Bool, Int, Int16, Int32, Int64, Float, Double, Decimal, Date, String, Data, UUID, URL





: , .





:





protocol PrimitiveAttributeType {}

protocol SupportedAttributeType {
    associatedtype P: PrimitiveAttributeType

    func encodePrimitive() -> P

    static func decode(primitive: P) -> Self
}
      
      



SupportedAttributeType Attribute





final class Attribute<T: ManagedObjectConvertible> {
    let encode: (T, NSManagedObject) -> Void
    let decode: (inout T, NSManagedObject) -> Void

    init<V: SupportedAttributeType>(keyPath: WritableKeyPath<T, V>, key: String) {
        self.encode = {
            $1.setValue($0[keyPath: keyPath].encodePrimitive(), forKey: key)
        }

        self.decode = {
            $0[keyPath: keyPath] = V.decode(primitive: $1.value(forKey: key) as! V.P)
        }
    }
}
      
      



Transformable, objc-.





NSManagedObject- - , , .





ManagedObjectConvertible . , data access DAO, DTO - data transfer .





NSManaged

NSManaged- DAO DTO, + DAO+DTO, , , NSManagedObject , . NSManaged- . DTO + ( ManagedObjectConvertible). :





+ raw NSManaged- + X = DAO+DTO





NSManaged raw - .





X - , , NSManaged-.





:





final class ManagedObject<T: ManagedObjectConvertible> {
    let instance: NSManagedObject

    ...
}
      
      



NSManaged , .





- , dynamicMemberLookup Swift.





ManagedObjectConvertible , - . , Keypaths . ManagedObject:





final class ManagedObject<T: ManagedObjectConvertible> {
    ...

    subscript<D: ManagedObjectConvertible>(
        keyPath: KeyPath<T.Relations, Relation<D>>
    ) -> ManagedObject<D> {
        let destinationName = T.relations[keyPath: keyPath]

        //     NSManaged API

        return .init(instance: ...)
    }
}
      
      



, , :





managedObject[keyPath: \.someRelation]







, - dynamicMemberLookup



:





@dynamicMemberLookup
final class ManagedObject<T: ManagedObjectConvertible> {
    ...

    subscript<D: ManagedObjectConvertible>(
        dynamicMember keyPath: KeyPath<T.Relations, Relation<D>>
    ) -> ManagedObject<D> { ... }
}
      
      



:





managedObject.someRelation







, - .





, :





"foo.x > 9 AND foo.y = 10"



\Foo.x > 9 && \Foo.y == 10



"foo.x > 9 AND foo.y = 10"







Attribute Equatable Comparable . .





>. KeyPath , - . \Foo.x > 9 "x > 9". - . ">". ManagedObjectConvertible Foo , . , :





final class Attribute<T: ManagedObjectConvertible> {
    let key: String
    let keyPath: PartialKeyPath<T>

    let encode: (T, NSManagedObject) -> Void
    let decode: (inout T, NSManagedObject) -> Void

    ...
}
      
      



, WritableKeyPath PartialKeyPath. , , Hashable. , , , .





, , KV-.





, . , Equatable / Comparable. , , (. SupportedAttributeType).





, , Equatable / Comparable:





func == <T: ManagedObjectConvertible, V: SupportedAttributeType>(
    keyPath: KeyPath<T, V>,
    value: V
) -> Predicate where V.PrimitiveAttributeType: Equatable {
    return .init(
        key: T.attributes.first(where: { $0.keyPath == keyPath })!.key,
        value: value.encodePrimitiveValue(),
        operator: "="
    )
}
      
      



Predicate - , .





. AND. "(\(left)) AND (\(right))"



.





, , swift .





, . , - , .





, Sworm , .





!








All Articles