Habr, olá! Meu nome é Geor, e estou desenvolvendo projetos iOS na Prisma Labs. Como vocês provavelmente entenderam, hoje falaremos sobre a cordata e muitos de vocês já se cansaram neste momento. Mas não se desespere, pois falaremos principalmente sobre a magia de Swift e sobre o metal. Piada - sobre metal em outro momento. A história será sobre como derrotamos o boilerplate NSManaged, reinventamos as migrações e tornamos a cordata ótima novamente.
Desenvolvedores, vamos.
Algumas palavras sobre motivação
É difícil trabalhar com uma cordata. Especialmente em nosso tempo rápido. Esta é uma estrutura muito antiga que foi criada como uma camada de dados com ênfase na otimização de E / S, que por padrão a tornou mais complexa do que outras formas de armazenamento de dados. Mas a produtividade do ferro com o tempo deixou de ser um gargalo e a complexidade da cordata, infelizmente, não foi a lugar nenhum. Em aplicativos modernos, muitas pessoas preferem outros frameworks ao cordata: Realm, GRDB (topo), etc. Ou eles apenas usam arquivos (por que não). Até mesmo a Apple em novos tutoriais usa serialização / desserialização codificável para persistência.
, (. 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 , .
!