Habr, halo! Nama saya Geor, dan saya sedang mengembangkan proyek iOS di Prisma Labs. Seperti yang mungkin Anda pahami, hari ini kita akan berbicara tentang cordata dan banyak dari Anda sudah bosan saat ini. Tapi jangan terburu-buru putus asa, karena kita akan berbicara sebagian besar tentang keajaiban Swift dan tentang logam. Lelucon - tentang logam lain kali. Ceritanya akan tentang bagaimana kami mengalahkan boilerplate NSManaged, menemukan kembali migrasi, dan membuat cordata menjadi hebat lagi.
Pengembang, ayolah.
Beberapa kata tentang motivasi
Sulit untuk bekerja dengan cordata. Apalagi di zaman kita yang serba cepat. Ini adalah kerangka kerja yang sangat lama yang dibuat sebagai lapisan data dengan penekanan pada optimasi I / O, yang secara default membuatnya lebih kompleks daripada cara lain untuk menyimpan data. Tetapi produktivitas besi dari waktu ke waktu tidak lagi menjadi hambatan, dan kompleksitas cordata, sayangnya, tidak ke mana-mana. Dalam aplikasi modern, banyak orang lebih memilih kerangka kerja lain daripada cordata: Realm, GRDB (atas), dll. Atau mereka hanya menggunakan file (mengapa tidak). Bahkan Apple dalam tutorial baru menggunakan serialisasi / deserialisasi Codable untuk kegigihan.
, (. 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 , .
!