Habr, bonjour ! Je m'appelle Geor et je développe des projets iOS chez Prisma Labs. Comme vous l'avez probablement compris, aujourd'hui, nous allons parler de la cordata et beaucoup d'entre vous se sont déjà ennuyés à ce moment-là. Mais ne vous précipitez pas pour désespérer, car nous parlerons surtout de la magie de Swift et du métal. Blague - sur le métal une autre fois. L'histoire sera sur la façon dont nous avons vaincu le passe-partout NSManged, réinventé les migrations et rendu à nouveau la cordata géniale.
Développeurs, allez.
Quelques mots sur la motivation
Il est difficile de travailler avec une cordée. Surtout à notre époque rapide. Il s'agit d'un cadre très ancien qui a été créé en tant que couche de données en mettant l'accent sur l'optimisation des E/S, ce qui le rendait par défaut plus complexe que d'autres moyens de stocker des données. Mais la productivité du fer au fil du temps a cessé d'être un goulot d'étranglement et la complexité des cordata, hélas, n'a disparu nulle part. Dans les applications modernes, de nombreuses personnes préfèrent d'autres frameworks aux cordata : Realm, GRDB (en haut), etc. Ou ils utilisent simplement des fichiers (pourquoi pas). Même Apple dans les nouveaux tutoriels utilise la sérialisation/désérialisation Codable pour la persistance.
, (. 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 , .
!