Skip to content

Instantly share code, notes, and snippets.

@KaQuMiQ
Last active June 18, 2020 08:00
Show Gist options
  • Select an option

  • Save KaQuMiQ/025a4e4d384b1d4dbe0086818ed9033d to your computer and use it in GitHub Desktop.

Select an option

Save KaQuMiQ/025a4e4d384b1d4dbe0086818ed9033d to your computer and use it in GitHub Desktop.
CodeData - CoreData from code
// MARK: - Storage.swift
import CoreData
public final class Storage {
private let queue: DispatchQueue
private let context: NSManagedObjectContext
private let schema: StorageSchema
public enum StoreType {
case sqlite
case inMemory
internal var coreDataType: String {
switch self {
case .sqlite: return NSSQLiteStoreType
case .inMemory: return NSInMemoryStoreType
}
}
}
public init(
name: String,
store: StoreType = .sqlite,
schema: StorageSchema,
migrations: Array<StorageMigration>
) throws {
do {
let storeURL = NSPersistentContainer.defaultDirectoryURL().appendingPathComponent("\(name).sqlite")
if case .sqlite = store {
try NSKeyedArchiver.archivedData(withRootObject: schema.managedObjectModel, requiringSecureCoding: true).write(to: storeURL.deletingLastPathComponent().appendingPathComponent("CodeDataModel_\(schema.version)"))
try Storage.migrateIfNeeded(storeURL, type: store, migrations: migrations)
} else { /**/ }
self.queue = DispatchQueue(label: "codeData.storage.\(name)")
self.schema = schema
let storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: schema.managedObjectModel)
try storeCoordinator
.addPersistentStore(
ofType: store.coreDataType,
configurationName: nil,
at: storeURL,
options: [NSIgnorePersistentStoreVersioningOption: true]
)
if case .sqlite = store {
try NSPersistentStoreCoordinator.setMetadata(["SchemaVersion" : schema.version], forPersistentStoreOfType: store.coreDataType, at: storeURL, options: nil)
} else { /**/ }
self.context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
self.context.mergePolicy = NSOverwriteMergePolicy
self.context.automaticallyMergesChangesFromParent = true
self.context.retainsRegisteredObjects = true
self.context.shouldDeleteInaccessibleFaults = true
self.context.persistentStoreCoordinator = storeCoordinator
} catch let error as StorageError {
throw error
} catch {
throw StorageError.internalError(error)
}
}
fileprivate static func migrateIfNeeded(
_ url: URL,
type: StoreType,
migrations: Array<StorageMigration>
) throws {
do {
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: type.coreDataType, at: url, options: nil)
guard var schemaVersion = metadata["SchemaVersion"] as? Int
else { fatalError("Missing SchemaVersion") }
for migration in migrations {
guard migration.version > schemaVersion else { continue }
try migration.migrate(url, type: type, version: schemaVersion)
schemaVersion = migration.version
}
} catch let error as StorageError {
throw error
} catch let error as NSError {
guard error.code == 260, error.domain == "NSCocoaErrorDomain"
else { throw StorageError.internalError(error) }
// ignore missing file error = there is no store yet so no migration is needed
}
}
}
public extension Storage {
func loadAsync<Entity: StorageEntity>(
_ type: Entity.Type,
where filter: StorageFilter = .all,
sorting: StorageSorting? = nil,
limit: Int = 0,
offset: Int = 0,
_ callback: @escaping ResultCallback<Array<Entity>, StorageError>
) {
queue.async { [weak self] in
guard let self = self else { return }
callback(self.unsafeLoad(type, where: filter, sorting: sorting, limit: limit, offset: offset))
}
}
func load<Entity: StorageEntity>(
_ type: Entity.Type,
where filter: StorageFilter = .all,
sorting: StorageSorting? = nil,
limit: Int = 0,
offset: Int = 0
) -> Result<Array<Entity>, StorageError> {
queue.sync { self.unsafeLoad(type, where: filter, sorting: sorting, limit: limit, offset: offset) }
}
func saveAsync<Entity: StorageEntity>(
_ entity: Entity,
_ callback: @escaping ResultCallback<Void, StorageError>
) {
queue.async { [weak self] in
guard let self = self else { return }
callback(self.unsafeSave(entity))
}
}
func save<Entity: StorageEntity>(
_ entity: Entity
) -> Result<Void, StorageError> {
queue.sync { unsafeSave(entity) }
}
func saveAsync<Entities>(
_ entities: Entities,
_ callback: @escaping ResultCallback<Void, StorageError>
) where Entities: Collection, Entities.Element: StorageEntity {
queue.async { [weak self] in
guard let self = self else { return }
callback(self.unsafeSave(entities))
}
}
func save<Entities>(
_ entities: Entities
) -> Result<Void, StorageError> where Entities: Collection, Entities.Element: StorageEntity {
queue.sync { self.unsafeSave(entities) }
}
func updateAsync<Entity: StorageEntity, Value>(
_ keyPath: WritableKeyPath<Entity, Value>,
to value: Value,
where filter: StorageFilter = .all,
_ callback: @escaping ResultCallback<Void, StorageError>
) {
queue.async { [weak self] in
guard let self = self else { return }
callback(self.unsafeUpdate(keyPath, to: value, where: filter))
}
}
func update<Entity: StorageEntity, Value>(
_ keyPath: WritableKeyPath<Entity, Value>,
to value: Value,
where filter: StorageFilter = .all
) -> Result<Void, StorageError> {
queue.sync { self.unsafeUpdate(keyPath, to: value, where: filter) }
}
func deleteAsync<Entity: StorageEntity>(
_ type: Entity.Type,
where filter: StorageFilter = .all,
_ callback: @escaping ResultCallback<Void, StorageError>
) {
queue.async { [weak self] in
guard let self = self else { return }
callback(self.unsafeDelete(type, where: filter))
}
}
func delete<Entity: StorageEntity>(
_ type: Entity.Type,
where filter: StorageFilter = .all
) -> Result<Void, StorageError> {
queue.sync { self.unsafeDelete(type, where: filter) }
}
}
private extension Storage {
func unsafeLoad<Entity: StorageEntity>(
_ type: Entity.Type,
where filter: StorageFilter = .all,
sorting: StorageSorting? = nil,
limit: Int = 0,
offset: Int = 0
) -> Result<Array<Entity>, StorageError> {
dispatchPrecondition(condition: .onQueue(queue))
do {
return try .success(
context
.fetch(
fetchRequest(
for: Entity.model.name,
predicate: filter.predicate,
sorting: sorting?.descriptors ?? [],
limit: limit,
offset: offset
)
)
.compactMap {
guard !$0.isDeleted else { return nil }
return try Entity(from: StorageEntityDecoder(managed: $0, context: context))
}
)
} catch let error as StorageError {
return .failure(error)
} catch {
return .failure(.internalError(error))
}
}
func unsafeSave<Entity: StorageEntity>(
_ entity: Entity
) -> Result<Void, StorageError> {
dispatchPrecondition(condition: .onQueue(queue))
do {
let managed = NSManagedObject(entity: type(of: entity).model.entityDescription, insertInto: context)
let encoder = StorageEntityEncoder(managed: managed, context: context)
try entity.encode(to: encoder)
context.insert(managed)
try context.save()
return .success(())
} catch let error as StorageError {
context.rollback()
return .failure(error)
} catch {
context.rollback()
return .failure(.internalError(error))
}
}
func unsafeSave<Entities>(
_ entities: Entities
) -> Result<Void, StorageError> where Entities: Collection, Entities.Element: StorageEntity {
dispatchPrecondition(condition: .onQueue(queue))
do {
try entities.forEach { entity in
let managed = NSManagedObject(entity: type(of: entity).model.entityDescription, insertInto: context)
let encoder = StorageEntityEncoder(managed: managed, context: context)
try entity.encode(to: encoder)
context.insert(managed)
}
try context.save()
return .success(())
} catch let error as StorageError {
context.rollback()
return .failure(error)
} catch {
context.rollback()
return .failure(.internalError(error))
}
}
func unsafeUpdate<Entity: StorageEntity, Value>(
_ keyPath: WritableKeyPath<Entity, Value>,
to value: Value,
where filter: StorageFilter = .all
) -> Result<Void, StorageError> {
dispatchPrecondition(condition: .onQueue(queue))
do {
try context
.fetch(
fetchRequest(
for: Entity.model.name,
predicate: filter.predicate
)
)
.compactMap { (managed: NSManagedObject) -> Entity? in
guard !managed.isDeleted else { return nil }
defer { context.delete(managed) }
return try Entity(from: StorageEntityDecoder(managed: managed, context: context))
}
.forEach { (entity: Entity) -> Void in
var entity = entity
entity[keyPath: keyPath] = value
let managed = NSManagedObject(entity: type(of: entity).model.entityDescription, insertInto: context)
let encoder = StorageEntityEncoder(managed: managed, context: context)
try entity.encode(to: encoder)
context.insert(managed)
}
try context.save()
return .success(())
} catch let error as StorageError {
context.rollback()
return .failure(error)
} catch {
context.rollback()
return .failure(.internalError(error))
}
}
func unsafeDelete<Entity: StorageEntity>(
_ type: Entity.Type,
where filter: StorageFilter = .all
) -> Result<Void, StorageError> {
dispatchPrecondition(condition: .onQueue(queue))
do {
try self.context
.fetch(
fetchRequest(
for: Entity.model.name,
predicate: filter.predicate
)
)
.forEach(self.context.delete)
try context.save()
return .success(())
} catch let error as StorageError {
context.rollback()
return .failure(error)
} catch {
context.rollback()
return .failure(.internalError(error))
}
}
}
private func fetchRequest(
for name: String,
predicate: NSPredicate? = nil,
sorting: [NSSortDescriptor] = [],
limit: Int = 0,
offset: Int = 0
) -> NSFetchRequest<NSManagedObject> {
let request = NSFetchRequest<NSManagedObject>(entityName: name)
request.returnsObjectsAsFaults = false
request.includesPropertyValues = true
request.predicate = predicate
request.sortDescriptors = sorting
request.fetchBatchSize = limit
request.fetchOffset = offset
return request
}
// MARK: - StorageError.swift
public enum StorageError: Error {
case dataDeleted
case dataInconsistency
case internalError(Error)
}
// MARK: - StorageSchema.swift
import CoreData
public struct StorageSchema {
public typealias Version = Int
internal let version: Version
internal let managedObjectModel: NSManagedObjectModel = NSManagedObjectModel()
public init(version: Version, entities: StorageEntity.Type...) {
self.version = version
managedObjectModel.entities = entities.map { $0.model.entityDescription }
}
}
// MARK: - StorageEntity.swift
public protocol StorageEntity: Codable {
static var model: StorageEntityModel { get }
}
// MARK: - StorageEntityModel.swift
import CoreData
public final class StorageEntityModel: Hashable {
public var name: String
public var fields: Set<StorageEntityField>
public init(
name: String,
fields: StorageEntityField...
) {
self.name = name
self.fields = Set(fields)
}
// you have to keep the same reference to the model through application lifetime
deinit { fatalError("Reference to \(Self.self) lost!") }
internal lazy var entityDescription: NSEntityDescription = {
let entityDescription: NSEntityDescription = .init()
entityDescription.name = name
entityDescription.isAbstract = false
let properties = fields.map(\.propertyDescription)
properties
.compactMap { $0 as? NSRelationshipDescription }
.forEach {
$0.inverseRelationship?.name += "_\(name)"
$0.inverseRelationship?.destinationEntity = entityDescription
$0.inverseRelationship?.inverseRelationship = $0
}
entityDescription.properties = properties
entityDescription.indexes = fields.compactMap { field in
guard field.isIndexed, let property = properties.first(where: { $0.name == field.name }) else { return nil }
return NSFetchIndexDescription(
name: "\(field.name)_idx",
elements: [
NSFetchIndexElementDescription(
property: property,
collationType: .binary
)
]
)
}
entityDescription.uniquenessConstraints = fields.compactMap { field in
guard field.isUnique else { return nil }
return [NSString(string: field.name)]
}
return entityDescription
}()
public func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(fields)
}
public static func == (lhs: StorageEntityModel, rhs: StorageEntityModel) -> Bool {
lhs.name == rhs.name && lhs.fields == rhs.fields
}
}
// MARK: - StorageEntityField.swift
import CoreData
public final class StorageEntityField: Hashable {
public let name: String
public let type: StorageEntityFieldType
public var isUnique: Bool { type.isUnique }
public var isIndexed: Bool { type.isIndexed }
public init(
key: CodingKey,
type: StorageEntityFieldType
) {
self.name = key.stringValue
self.type = type
}
internal lazy var propertyDescription: NSPropertyDescription = {
let property: NSPropertyDescription
switch type {
case let .bool(optional, indexed):
let attribute: NSAttributeDescription = NSAttributeDescription()
attribute.attributeType = .booleanAttributeType
attribute.isOptional = optional
property = attribute
case let .string(optional, indexed, _):
let attribute: NSAttributeDescription = NSAttributeDescription()
attribute.attributeType = .stringAttributeType
attribute.isOptional = optional
property = attribute
case let .int(optional, indexed, _):
let attribute: NSAttributeDescription = NSAttributeDescription()
attribute.attributeType = .integer64AttributeType
attribute.isOptional = optional
property = attribute
case let .double(optional, indexed):
let attribute: NSAttributeDescription = NSAttributeDescription()
attribute.attributeType = .doubleAttributeType
attribute.isOptional = optional
property = attribute
case let .array(type, optional, indexed):
let attribute: NSAttributeDescription = NSAttributeDescription()
attribute.attributeType = .transformableAttributeType
attribute.valueTransformerName = "NSSecureUnarchiveFromDataTransformer"
attribute.isOptional = optional
property = attribute
case let .data(optional, indexed, external):
let attribute: NSAttributeDescription = NSAttributeDescription()
attribute.attributeType = .binaryDataAttributeType
attribute.allowsExternalBinaryDataStorage = external
attribute.isOptional = optional
property = attribute
case let .entity(type, optional, indexed):
let relationship: NSRelationshipDescription = NSRelationshipDescription()
relationship.deleteRule = .cascadeDeleteRule
relationship.destinationEntity = type.model.entityDescription
relationship.minCount = optional ? 0 : 1
relationship.maxCount = 1
relationship.isOptional = optional
let inverseRelationship: NSRelationshipDescription = NSRelationshipDescription()
inverseRelationship.name = "_inverse_\(name)"
inverseRelationship.deleteRule = optional ? .nullifyDeleteRule : .cascadeDeleteRule
inverseRelationship.minCount = 0
inverseRelationship.maxCount = 1
inverseRelationship.isOptional = true
relationship.inverseRelationship = inverseRelationship
type.model.entityDescription.properties.append(inverseRelationship)
property = relationship
case let .list(type, indexed):
let relationship: NSRelationshipDescription = NSRelationshipDescription()
relationship.deleteRule = .nullifyDeleteRule
relationship.destinationEntity = type.model.entityDescription
relationship.minCount = 0
relationship.maxCount = 0
relationship.isOptional = true
let inverseRelationship: NSRelationshipDescription = NSRelationshipDescription()
inverseRelationship.name = "_inverse_\(name)"
inverseRelationship.deleteRule = .nullifyDeleteRule
inverseRelationship.minCount = 0
inverseRelationship.maxCount = 0
inverseRelationship.isOptional = true
relationship.inverseRelationship = inverseRelationship
type.model.entityDescription.properties.append(inverseRelationship)
property = relationship
}
property.name = name
return property
}()
public func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(type)
}
public static func == (lhs: StorageEntityField, rhs: StorageEntityField) -> Bool {
lhs.name == rhs.name && lhs.type == rhs.type
}
}
// MARK: - StorageEntityFieldType.swift
import CoreData
public enum StorageEntityFieldType: Hashable {
case bool(optional: Bool, indexed: Bool)
case string(optional: Bool, indexed: Bool, unique: Bool)
case int(optional: Bool, indexed: Bool, unique: Bool) // TODO: int8/16/32/64 ??
case double(optional: Bool, indexed: Bool) // TODO: float ??
/// - warning: it cannot be filtered only saved and loaded
case array(of: (Any & Codable).Type, optional: Bool, indexed: Bool)
// TODO: dictionary of
case data(optional: Bool, indexed: Bool, external: Bool)
case entity(of: StorageEntity.Type, optional: Bool, indexed: Bool)
/// - warning: it cannot be optional - it becomes empty array instead
case list(of: StorageEntity.Type, indexed: Bool)
public var isOptional: Bool {
switch self {
case
let .bool(optional, _),
let .string(optional, _, _),
let .int(optional, _, _),
let .double(optional, _),
let .array(_, optional, _),
let .data(optional, _, _),
let .entity(_, optional, _):
return optional
case .list:
return false // TODO: to verify
}
}
public var isIndexed: Bool {
switch self {
case
let .bool(_, indexed),
let .string(_, indexed, _),
let .int(_, indexed, _),
let .double(_, indexed),
let .array(_, _, indexed),
let .data(_, indexed, _),
let .entity(_, _, indexed),
let .list(_, indexed):
return indexed
}
}
public var isUnique: Bool {
switch self {
case
.bool,
.double,
.array,
.data,
.entity,
.list:
return false
case let .string(_, _, unique),
let .int(_, _, unique):
return unique
}
}
public var isRelation: Bool {
switch self {
case
.bool,
.string,
.int,
.double,
.array,
.data:
return false
case .entity, .list:
return true
}
}
public func hash(into hasher: inout Hasher) {
switch self {
case let .bool(optional, indexed):
hasher.combine(optional)
hasher.combine(indexed)
hasher.combine("bool")
case let .string(optional, indexed, unique):
hasher.combine(optional)
hasher.combine(indexed)
hasher.combine(unique)
hasher.combine("string")
case let .int(optional, indexed, unique):
hasher.combine(optional)
hasher.combine(indexed)
hasher.combine(unique)
hasher.combine("int")
case let .double(optional, indexed):
hasher.combine(optional)
hasher.combine(indexed)
hasher.combine("double")
case let .array(type, optional, indexed):
hasher.combine(optional)
hasher.combine(indexed)
hasher.combine("\(type)")
hasher.combine("array")
case let .data(optional, indexed, external):
hasher.combine(optional)
hasher.combine(indexed)
hasher.combine(external)
hasher.combine("data")
case let .entity(type, optional, indexed):
hasher.combine(optional)
hasher.combine(indexed)
hasher.combine("\(type)")
hasher.combine("entity")
case let .list(type, indexed):
hasher.combine(indexed)
hasher.combine("\(type)")
hasher.combine("list")
}
}
public static func == (lhs: StorageEntityFieldType, rhs: StorageEntityFieldType) -> Bool {
switch (lhs, rhs) {
case let (.bool(lopt, lind), .bool(ropt, rind)):
return lopt == ropt && lind == rind
case let (.string(lopt, lind, luni), .string(ropt, rind, runi)):
return lopt == ropt && lind == rind && luni == runi
case let (.int(lopt, lind, luni), .int(ropt, rind, runi)):
return lopt == ropt && lind == rind && luni == runi
case let (.double(lopt, lind), .double(ropt, rind)):
return lopt == ropt && lind == rind
case let (.array(ltype, lopt, lind), .array(rtype, ropt, rind)):
return lopt == ropt && ltype == rtype && lind == rind
case let (.data(lopt, lind, lext), .data(ropt, rind, rext)):
return lopt == ropt && lind == rind && lext == rext
case let (.entity(ltype, lopt, lind), .entity(rtype, ropt, rind)):
return lopt == ropt && ltype == rtype && lind == rind
case let (.list(ltype, lind), .list(rtype, rind)):
return ltype == rtype && lind == rind
case _:
return false
}
}
}
// MARK: - StorageFilter.swift
import CoreData
public struct StorageFilter {
internal let predicate: NSPredicate
public init(_ predicate: NSPredicate) {
self.predicate = predicate
}
}
public extension StorageFilter {
static var all: StorageFilter { StorageFilter(NSPredicate(value: true)) }
init<Subject, Value>(
_ keyPath: KeyPath<Subject, Value>,
equal: Value
) where Subject: StorageEntity, Value: Equatable {
guard let keyPathString = keyPath._kvcKeyPathString
else { fatalError("Unsupported keypath, please mark with `@objc` or use keyPathString") }
self.init(
keyPathString: keyPathString,
equal: equal
)
}
init<Value>(
keyPathString: String,
equal: Value
) where Value: Equatable {
self.init(
NSPredicate(
format: "%K == %@",
argumentArray: [keyPathString, equal]
)
)
}
init<Subject, Value>(
_ keyPath: KeyPath<Subject, Value>,
greaterThanOrEqualTo value: Value
) where Subject: StorageEntity, Value: Comparable {
guard let keyPathString = keyPath._kvcKeyPathString
else { fatalError("Unsupported keypath, please mark with `@objc` or use keyPathString") }
self.init(
keyPathString: keyPathString,
greaterThanOrEqualTo: value
)
}
init<Value>(
keyPathString: String,
greaterThanOrEqualTo value: Value
) where Value: Comparable {
self.init(
NSPredicate(
format: "%K >= %@",
argumentArray: [keyPathString, value]
)
)
}
init<Subject, Value>(
_ keyPath: KeyPath<Subject, Value>,
greaterThan value: Value
) where Subject: StorageEntity, Value: Comparable {
guard let keyPathString = keyPath._kvcKeyPathString
else { fatalError("Unsupported keypath, please mark with `@objc` or use keyPathString") }
self.init(
keyPathString: keyPathString,
greaterThan: value
)
}
init<Value>(
keyPathString: String,
greaterThan value: Value
) where Value: Comparable {
self.init(
NSPredicate(
format: "%K > %@",
argumentArray: [keyPathString, value]
)
)
}
init<Subject, Value>(
_ keyPath: KeyPath<Subject, Value>,
lessThanOrEqualTo value: Value
) where Subject: StorageEntity, Value: Comparable {
guard let keyPathString = keyPath._kvcKeyPathString
else { fatalError("Unsupported keypath, please mark with `@objc` or use keyPathString") }
self.init(
keyPathString: keyPathString,
lessThanOrEqualTo: value
)
}
init<Value>(
keyPathString: String,
lessThanOrEqualTo value: Value
) where Value: Comparable {
self.init(
NSPredicate(
format: "%K <= %@",
argumentArray: [keyPathString, value]
)
)
}
init<Subject, Value>(
_ keyPath: KeyPath<Subject, Value>,
lessThan value: Value
) where Subject: StorageEntity, Value: Comparable {
guard let keyPathString = keyPath._kvcKeyPathString
else { fatalError("Unsupported keypath, please mark with `@objc` or use keyPathString") }
self.init(
keyPathString: keyPathString,
lessThan: value
)
}
init<Value>(
keyPathString: String,
lessThan value: Value
) where Value: Comparable {
self.init(
NSPredicate(
format: "%K < %@",
argumentArray: [keyPathString, value]
)
)
}
init<Subject>(
_ keyPath: KeyPath<Subject, String>,
contains: String
) where Subject: StorageEntity {
guard let keyPathString = keyPath._kvcKeyPathString
else { fatalError("Unsupported keypath, please mark with `@objc` or use keyPathString") }
self.init(
keyPathString: keyPathString,
contains: contains
)
}
init(
keyPathString: String,
contains: String
) {
self.init(
NSPredicate(
format: "%K CONTAINS[cd] %@",
argumentArray: [keyPathString, contains]
)
)
}
init<Subject, Value>(
_ keyPath: KeyPath<Subject, Value>,
containsAny valueSet: Set<Value>
) where Subject: StorageEntity, Value: Hashable {
guard let keyPathString = keyPath._kvcKeyPathString
else { fatalError("Unsupported keypath, please mark with `@objc` or use keyPathString") }
self.init(
keyPathString: keyPathString,
containsAny: valueSet
)
}
init<Value>(
keyPathString: String,
containsAny valueSet: Set<Value>
) where Value: Hashable {
self.init(
NSPredicate(
format: "%K IN %@",
argumentArray: [keyPathString, NSSet(set: valueSet)]
)
)
}
init<Subject, Element, Elements>(
_ keyPath: KeyPath<Subject, Elements>,
containsElement element: Element
) where Subject: StorageEntity, Elements: Collection, Elements.Element == Element, Element: Equatable {
guard let keyPathString = keyPath._kvcKeyPathString
else { fatalError("Unsupported keypath, please mark with `@objc` or use keyPathString") }
self.init(
keyPathString: keyPathString,
containsElement: element
)
}
init<Element>(
keyPathString: String,
containsElement element: Element
) where Element: Equatable {
self.init(
NSPredicate(
format: "%@ IN %K",
argumentArray: [element, keyPathString]
)
)
}
init(compound filters: StorageFilter...) {
self.init(NSCompoundPredicate(andPredicateWithSubpredicates: filters.map(\.predicate)))
}
}
// MARK: - StorageSorting.swift
import Foundation
public struct StorageSorting {
internal let descriptors: [NSSortDescriptor]
internal init(_ descriptors: [NSSortDescriptor]) {
self.descriptors = descriptors
}
}
public extension StorageSorting {
init<Subject, Value>(
_ keyPath: KeyPath<Subject, Value>,
ascending: Bool = true
) where Subject: StorageEntity, Value: Comparable {
self.init([NSSortDescriptor(keyPath: keyPath, ascending: ascending)])
}
init(
key: String,
ascending: Bool = true
) {
self.init([NSSortDescriptor(key: key, ascending: ascending)])
}
}
// MARK: - StorageMigration.swift
import CoreData
public struct StorageMigration {
public let version: StorageSchema.Version
private let entityMigrations: Array<StorageEntityMigration.Type>
public init(
version: StorageSchema.Version,
entityMigrations: Array<StorageEntityMigration.Type>
) {
self.version = version
self.entityMigrations = entityMigrations
}
internal func migrate(_ url: URL, type: Storage.StoreType, version currentVersion: StorageSchema.Version) throws {
guard case .sqlite = type else { return }
let tempURL = url.appendingPathExtension("migration")
let fileManager = FileManager.default
if fileManager.fileExists(atPath: tempURL.path) {
try fileManager.removeItem(at: tempURL)
// tempURL + "-wal" and tempURL + "-shm" mitght be also there to remove
} else { /**/ }
// TODO: we might have more than one store - we should allow multiple data models for that
let sourceModel = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(Data.init(contentsOf: url.deletingLastPathComponent().appendingPathComponent("CodeDataModel_\(currentVersion)"))) as! NSManagedObjectModel
let destinationModel = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(Data.init(contentsOf: url.deletingLastPathComponent().appendingPathComponent("CodeDataModel_\(version)"))) as! NSManagedObjectModel
let mappingModel = try NSMappingModel.inferredMappingModel(forSourceModel: sourceModel, destinationModel: destinationModel)
mappingModel.entityMappings = mappingModel.entityMappings!
.map { mapping in
guard let migration = entityMigrations.first(where: { $0.sourceEntityName == mapping.sourceEntityName }) else { return mapping }
return migration.mapping(from: mapping)
}
let migrationManager = NSMigrationManager(
sourceModel: sourceModel,
destinationModel: destinationModel
)
try migrationManager.migrateStore(
from: url,
sourceType: type.coreDataType,
options: [NSIgnorePersistentStoreVersioningOption: true],
with: mappingModel,
toDestinationURL: tempURL,
destinationType: type.coreDataType,
destinationOptions: [NSIgnorePersistentStoreVersioningOption: true]
)
try NSPersistentStoreCoordinator.setMetadata(["SchemaVersion" : version], forPersistentStoreOfType: type.coreDataType, at: tempURL, options: nil)
try NSPersistentStoreCoordinator().replacePersistentStore(
at: url,
destinationOptions: nil,
withPersistentStoreFrom: tempURL,
sourceOptions: nil,
ofType: type.coreDataType
)
}
}
// MARK: - StorageEntityMigration.swift
import CoreData
open class StorageEntityMigration: NSEntityMigrationPolicy {
open class var sourceEntityName: String? { destinationEntityName }
open class var destinationEntityName: String { fatalError("You have to override this property") }
internal class func mapping(from currentMapping: NSEntityMapping) -> NSEntityMapping {
let mapping = NSEntityMapping()
mapping.mappingType = .customEntityMappingType
mapping.name = "\(destinationEntityName)Mapping"
mapping.entityMigrationPolicyClassName = "\(Self.self)"
mapping.sourceEntityName = sourceEntityName
mapping.sourceEntityVersionHash = currentMapping.sourceEntityVersionHash
mapping.destinationEntityName = destinationEntityName
mapping.destinationEntityVersionHash = currentMapping.destinationEntityVersionHash
mapping.sourceExpression = currentMapping.sourceExpression
mapping.attributeMappings = currentMapping.attributeMappings
mapping.relationshipMappings = currentMapping.relationshipMappings
return mapping
}
open func migrate(from entityFrom: StorageDynamicEntity, to entityTo: StorageDynamicEntity) throws -> Bool {
fatalError("You have to override this method")
}
override open func createDestinationInstances(
forSource source: NSManagedObject,
in mapping: NSEntityMapping,
manager: NSMigrationManager
) throws {
let destination = NSEntityDescription.insertNewObject(
forEntityName: mapping.destinationEntityName!,
into: manager.destinationContext
)
for attribute in mapping.attributeMappings ?? [] {
guard
let name = attribute.name,
let valueExpression = attribute.valueExpression
else { continue }
let value: Any? = valueExpression.expressionValue(
with: source,
context: ["source": source]
)
if value is NSNull || value == nil {
destination.setNilValueForKey(name)
} else {
destination.setValue(value, forKey: name)
}
}
guard try migrate(
from: StorageDynamicEntity(source: source),
to: StorageDynamicEntity(source: destination)
)
else { return manager.destinationContext.delete(destination) }
manager.associate(
sourceInstance: source,
withDestinationInstance: destination,
for: mapping
)
}
}
// MARK: - StorageDynamicEntity.swift
import Foundation
@dynamicMemberLookup
public final class StorageDynamicEntity {
internal let source: NSObject
internal init(source: NSObject) {
self.source = source
}
public subscript<Literal>(dynamicMember: Literal) -> Bool?
where Literal: ExpressibleByStringLiteral & LosslessStringConvertible {
get { (source.value(forKey: String(dynamicMember)) as? NSNumber)?.boolValue }
set { source.setValue(newValue.map(NSNumber.init(value:)), forKey: String(dynamicMember))}
}
public subscript<Literal>(dynamicMember dynamicMember: Literal) -> String?
where Literal: ExpressibleByStringLiteral & LosslessStringConvertible {
get { (source.value(forKey: String(dynamicMember)) as? String) }
set { source.setValue(newValue, forKey: String(dynamicMember))}
}
public subscript<Literal>(dynamicMember dynamicMember: Literal) -> Int?
where Literal: ExpressibleByStringLiteral & LosslessStringConvertible {
get { (source.value(forKey: String(dynamicMember)) as? NSNumber)?.intValue }
set { source.setValue(newValue.map(NSNumber.init(value:)), forKey: String(dynamicMember))}
}
public subscript<Literal>(dynamicMember dynamicMember: Literal) -> Double?
where Literal: ExpressibleByStringLiteral & LosslessStringConvertible {
get { (source.value(forKey: String(dynamicMember)) as? NSNumber)?.doubleValue }
set { source.setValue(newValue.map(NSNumber.init(value:)), forKey: String(dynamicMember))}
}
public subscript<Literal, Element>(dynamicMember dynamicMember: Literal) -> Array<Element>?
where Literal: ExpressibleByStringLiteral & LosslessStringConvertible, Element: Codable {
get { (source.value(forKey: String(dynamicMember)) as? NSArray) as? Array<Element> }
set { source.setValue(newValue, forKey: String(dynamicMember))}
}
public subscript<Literal>(dynamicMember dynamicMember: Literal) -> StorageDynamicEntity?
where Literal: ExpressibleByStringLiteral & LosslessStringConvertible {
get { (source.value(forKey: String(dynamicMember)) as? NSObject).map(StorageDynamicEntity.init(source:)) }
set { source.setValue(newValue, forKey: String(dynamicMember))}
}
}
// MARK: - ResultCallback.swift
public typealias ResultCallback<Success, Failure: Error> = (Result<Success, Failure>) -> Void
// MARK: - StorageEntityEncoder.swift
import Foundation
import CoreData
internal final class StorageEntityEncoder: Encoder {
private let managed: NSManagedObject
private let context: NSManagedObjectContext
public var codingPath: [CodingKey] = []
public var userInfo: [CodingUserInfoKey : Any] { fatalError("Unsupported operation") }
internal init(managed: NSManagedObject, context: NSManagedObjectContext) {
self.managed = managed
self.context = context
}
public func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
let container = CoreDataManagedContainer<Key>(managed: managed, context: context)
container.codingPath = codingPath
return KeyedEncodingContainer(container)
}
public func unkeyedContainer() -> UnkeyedEncodingContainer {
guard !codingPath.isEmpty else { fatalError("Unsupported operation") }
let list = CoreDataManagedListContainer(managed: managed, context: context)
list.codingPath = codingPath
return list
}
public func singleValueContainer() -> SingleValueEncodingContainer {
fatalError("Unsupported operation")
}
internal final class CoreDataManagedContainer<Key: CodingKey>: KeyedEncodingContainerProtocol {
private let managed: NSManagedObject
private let context: NSManagedObjectContext
internal var codingPath: [CodingKey] = []
internal var allKeys: [Key] { fatalError("Unsupported operation") }
internal init(managed: NSManagedObject, context: NSManagedObjectContext) {
self.managed = managed
self.context = context
}
internal func encodeNil(forKey key: Key) throws {
managed.setPrimitiveValue(nil, forKey: key.stringValue)
}
internal func encode(_ value: Bool, forKey key: Key) throws {
managed.setPrimitiveValue(value, forKey: key.stringValue)
}
internal func encode(_ value: String, forKey key: Key) throws {
managed.setPrimitiveValue(value, forKey: key.stringValue)
}
internal func encode(_ value: Double, forKey key: Key) throws {
managed.setPrimitiveValue(value, forKey: key.stringValue)
}
internal func encode(_ value: Float, forKey key: Key) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Int, forKey key: Key) throws {
managed.setPrimitiveValue(value, forKey: key.stringValue)
}
internal func encode(_ value: Int8, forKey key: Key) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Int16, forKey key: Key) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Int32, forKey key: Key) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Int64, forKey key: Key) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: UInt, forKey key: Key) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: UInt8, forKey key: Key) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: UInt16, forKey key: Key) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: UInt32, forKey key: Key) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: UInt64, forKey key: Key) throws {
fatalError("Unsupported operation")
}
internal func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
switch value {
case let entity as StorageEntity:
let relation = NSManagedObject(entity: type(of: entity).model.entityDescription, insertInto: context)
let encoder = StorageEntityEncoder(managed: relation, context: context)
try entity.encode(to: encoder)
relation.setValue(managed, forKey: "_inverse_\(key.stringValue)_\(managed.entity.name!)")
managed.setValue(relation, forKey: key.stringValue)
case let string as String:
managed.setPrimitiveValue(string, forKey: key.stringValue)
case let int as Int:
managed.setPrimitiveValue(int, forKey: key.stringValue)
case let double as Double:
managed.setPrimitiveValue(double, forKey: key.stringValue)
case let bool as Bool:
managed.setPrimitiveValue(bool, forKey: key.stringValue)
case let _ as Array<Any & StorageEntity>:
let encoder = StorageEntityEncoder(managed: managed, context: context)
encoder.codingPath = codingPath + [key]
try value.encode(to: encoder)
case let array as Array<Any>:
managed.setValue(array, forKey: key.stringValue)
case let data as Data:
managed.setValue(data, forKey: key.stringValue)
case nil:
managed.setNilValueForKey(key.stringValue)
case _:
fatalError("Unsupported operation")
}
}
internal func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
guard let entity = managed.entity.relationshipsByName[key.stringValue]?.entity else { fatalError("Unsupported operation") }
let relation = NSManagedObject(entity: entity, insertInto: context)
managed.setValue(relation, forKey: key.stringValue)
relation.setValue(managed, forKey: "_inverse_\(key.stringValue)_\(managed.entity.name!)")
let container = CoreDataManagedContainer<NestedKey>(managed: relation, context: context)
container.codingPath = codingPath + [key]
return KeyedEncodingContainer(container)
}
internal func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
let list = CoreDataManagedListContainer(managed: managed, context: context)
list.codingPath = codingPath + [key]
return list
}
internal func superEncoder() -> Encoder {
fatalError("Unsupported operation")
}
internal func superEncoder(forKey key: Key) -> Encoder {
fatalError("Unsupported operation")
}
}
internal final class CoreDataManagedListContainer: UnkeyedEncodingContainer {
internal var codingPath: [CodingKey] = []
internal var count: Int { managed.mutableSetValue(forKey: stringKey).count }
internal var stringKey: String { codingPath.last!.stringValue }
private let managed: NSManagedObject
private let context: NSManagedObjectContext
internal init(managed: NSManagedObject, context: NSManagedObjectContext) {
self.managed = managed
self.context = context
}
internal func encode<T>(_ value: T) throws where T : Encodable {
guard let entity = value as? StorageEntity else { throw StorageError.dataInconsistency }
let relation = NSManagedObject(entity: type(of: entity).model.entityDescription, insertInto: context)
let encoder = StorageEntityEncoder(managed: relation, context: context)
try entity.encode(to: encoder)
managed.mutableSetValue(forKey: stringKey).add(relation)
relation.mutableSetValue(forKey: "_inverse_\(stringKey)_\(managed.entity.name!)").add(managed)
}
internal func encodeNil() throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Bool) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: String) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Double) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Float) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Int) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Int8) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Int16) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Int32) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: Int64) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: UInt) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: UInt8) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: UInt16) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: UInt32) throws {
fatalError("Unsupported operation")
}
internal func encode(_ value: UInt64) throws {
fatalError("Unsupported operation")
}
internal func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
guard let entity = managed.entity.relationshipsByName[stringKey]?.entity else { fatalError("Unsupported operation") }
let relation = NSManagedObject(entity: entity, insertInto: context)
managed.mutableSetValue(forKey: stringKey).add(relation)
relation.mutableSetValue(forKey: "_inverse_\(stringKey)_\(managed.entity.name!)").add(managed)
let container = CoreDataManagedContainer<NestedKey>(managed: relation, context: context)
container.codingPath = codingPath
return KeyedEncodingContainer(container)
}
internal func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
fatalError("Unsupported operation")
}
internal func superEncoder() -> Encoder {
fatalError("Unsupported operation")
}
}
}
// MARK: - StorageEntityDecoder.swift
import Foundation
import CoreData
internal final class StorageEntityDecoder: Decoder {
private let managed: NSManagedObject
private let context: NSManagedObjectContext
public var codingPath: [CodingKey] = []
public var userInfo: [CodingUserInfoKey : Any] { fatalError("Unsupported operation") }
internal init(managed: NSManagedObject, context: NSManagedObjectContext) {
assert(!managed.isDeleted)
self.managed = managed
self.context = context
}
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
let container = CoreDataManagedContainer<Key>(managed: managed, context: context)
container.codingPath = codingPath
return KeyedDecodingContainer(container)
}
public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
guard !codingPath.isEmpty else { fatalError("Unsupported operation") }
let container = CoreDataManagedListContainer(managed: managed, context: context)
container.codingPath = codingPath
return container
}
public func singleValueContainer() throws -> SingleValueDecodingContainer {
fatalError("Unsupported operation")
}
internal final class CoreDataManagedContainer<Key: CodingKey>: KeyedDecodingContainerProtocol {
private let managed: NSManagedObject
private let context: NSManagedObjectContext
internal var codingPath: [CodingKey] = []
internal var allKeys: [Key] { fatalError("Unsupported operation") }
internal init(managed: NSManagedObject, context: NSManagedObjectContext) {
self.managed = managed
self.context = context
}
internal func contains(_ key: Key) -> Bool {
managed.entity.attributesByName.keys.contains(key.stringValue)
|| managed.entity.relationshipsByName.keys.contains(key.stringValue)
}
internal func decodeNil(forKey key: Key) throws -> Bool {
managed.primitiveValue(forKey: key.stringValue) == nil
|| managed.value(forKey: key.stringValue) == nil
}
internal func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
guard let value = managed.primitiveValue(forKey: key.stringValue) as? Bool
else { throw StorageError.dataInconsistency }
return value
}
internal func decode(_ type: String.Type, forKey key: Key) throws -> String {
guard let value = managed.primitiveValue(forKey: key.stringValue) as? String
else { throw StorageError.dataInconsistency }
return value
}
internal func decode(_ type: Double.Type, forKey key: Key) throws -> Double {
guard let value = managed.primitiveValue(forKey: key.stringValue) as? Double
else { throw StorageError.dataInconsistency }
return value
}
internal func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
fatalError("Unsupported operation")
}
internal func decode(_ type: Int.Type, forKey key: Key) throws -> Int {
guard let value = managed.primitiveValue(forKey: key.stringValue) as? Int
else { throw StorageError.dataInconsistency }
return value
}
internal func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 {
fatalError("Unsupported operation")
}
internal func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 {
fatalError("Unsupported operation")
}
internal func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 {
fatalError("Unsupported operation")
}
internal func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 {
fatalError("Unsupported operation")
}
internal func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt {
fatalError("Unsupported operation")
}
internal func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 {
fatalError("Unsupported operation")
}
internal func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 {
fatalError("Unsupported operation")
}
internal func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 {
fatalError("Unsupported operation")
}
internal func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 {
fatalError("Unsupported operation")
}
internal func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
switch managed.value(forKey: key.stringValue) {
case let relation as NSManagedObject:
guard !relation.isDeleted else { throw StorageError.dataDeleted }
let decoder = StorageEntityDecoder(managed: relation, context: context)
decoder.codingPath = codingPath + [key]
return try T(from: decoder)
case let value as T:
return value
case _:
let decoder = StorageEntityDecoder(managed: managed, context: context)
decoder.codingPath = codingPath + [key]
return try T.init(from: decoder)
}
}
internal func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
guard let relation = managed.value(forKey: key.stringValue) as? NSManagedObject, !relation.isDeleted else { throw StorageError.dataInconsistency }
let container = CoreDataManagedContainer<NestedKey>(managed: relation, context: context)
container.codingPath = codingPath + [key]
return KeyedDecodingContainer(container)
}
internal func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
let listContainer = CoreDataManagedListContainer(managed: managed, context: context)
listContainer.codingPath = codingPath + [key]
return listContainer
}
internal func superDecoder() throws -> Decoder {
fatalError("Unsupported operation")
}
internal func superDecoder(forKey key: Key) throws -> Decoder {
fatalError("Unsupported operation")
}
}
internal final class CoreDataManagedListContainer: UnkeyedDecodingContainer {
internal var codingPath: [CodingKey] = []
internal var count: Int? { elements.count }
internal var isAtEnd: Bool { count.map { currentIndex >= $0 } ?? true }
internal var currentIndex: Int = 0
private let managed: NSManagedObject
private lazy var elements: Array<Any> = {
guard let key = codingPath.last else { fatalError("Unsupported operation") }
let value = managed.value(forKey: key.stringValue)
if let array = value as? NSArray {
return Array<Any>(array)
} else if let set = value as? NSSet {
return Array(set)
} else if value == nil {
return Array()
} else {
fatalError("Unsupported operation")
}
}()
private let context: NSManagedObjectContext
internal init(managed: NSManagedObject, context: NSManagedObjectContext) {
assert(!managed.isDeleted)
self.managed = managed
self.context = context
}
internal func decodeNil() throws -> Bool {
fatalError("Unsupported operation") // ????
}
internal func decode(_ type: Bool.Type) throws -> Bool {
guard let value = elements[currentIndex] as? Bool else { throw StorageError.dataInconsistency }
return value
}
internal func decode(_ type: String.Type) throws -> String {
guard let value = elements[currentIndex] as? String else { throw StorageError.dataInconsistency }
return value
}
internal func decode(_ type: Double.Type) throws -> Double {
guard let value = elements[currentIndex] as? Double else { throw StorageError.dataInconsistency }
return value
}
internal func decode(_ type: Float.Type) throws -> Float {
fatalError("Unsupported operation")
}
internal func decode(_ type: Int.Type) throws -> Int {
guard let value = elements[currentIndex] as? Int else { throw StorageError.dataInconsistency }
return value
}
internal func decode(_ type: Int8.Type) throws -> Int8 {
fatalError("Unsupported operation")
}
internal func decode(_ type: Int16.Type) throws -> Int16 {
fatalError("Unsupported operation")
}
internal func decode(_ type: Int32.Type) throws -> Int32 {
fatalError("Unsupported operation")
}
internal func decode(_ type: Int64.Type) throws -> Int64 {
fatalError("Unsupported operation")
}
internal func decode(_ type: UInt.Type) throws -> UInt {
fatalError("Unsupported operation")
}
internal func decode(_ type: UInt8.Type) throws -> UInt8 {
fatalError("Unsupported operation")
}
internal func decode(_ type: UInt16.Type) throws -> UInt16 {
fatalError("Unsupported operation")
}
internal func decode(_ type: UInt32.Type) throws -> UInt32 {
fatalError("Unsupported operation")
}
internal func decode(_ type: UInt64.Type) throws -> UInt64 {
fatalError("Unsupported operation")
}
internal func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
guard type is StorageEntity.Type else { fatalError("Unsupported operation") }
guard let relation = elements[currentIndex] as? NSManagedObject else { throw StorageError.dataInconsistency }
currentIndex += 1
guard !relation.isDeleted else { throw StorageError.dataDeleted }
let decoder = StorageEntityDecoder(managed: relation, context: context)
decoder.codingPath = codingPath
return try T(from: decoder)
}
internal func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
guard let relation = elements[currentIndex] as? NSManagedObject else { throw StorageError.dataInconsistency }
currentIndex += 1
guard !relation.isDeleted else { throw StorageError.dataDeleted }
let container = CoreDataManagedContainer<NestedKey>(managed: relation, context: context)
container.codingPath = codingPath
return KeyedDecodingContainer(container)
}
internal func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
fatalError("Unsupported operation")
}
internal func superDecoder() throws -> Decoder {
fatalError("Unsupported operation")
}
}
}
@KaQuMiQ
Copy link
Author

KaQuMiQ commented Jun 18, 2020

Example

sample models:

struct SomeStruct: StorageEntity {
  
  static var model: StorageEntityModel = {
    StorageEntityModel(
      name: "\(Self.self)",
      fields:
      StorageEntityField(key: CodingKeys.intArrayField, type: .array(of: Int.self, optional: false, indexed: false)),
      StorageEntityField(key: CodingKeys.stringField, type: .string(optional: false, indexed: false, unique: false))
    )
  }()
  
  var intArrayField: Array<Int>
  var stringField: String
}

struct RelationStruct: StorageEntity {
  
  static var model: StorageEntityModel = {
    StorageEntityModel(
      name: "\(Self.self)",
      fields:
      StorageEntityField(key: CodingKeys.relation, type: .entity(of: SomeStruct.self, optional: true, indexed: false))
    )
  }()
  
  var relation: SomeStruct?
}

store setup:

let store: Storage = try! Storage(
      name: "CodeData",
      store: .sqlite,
      schema: StorageSchema(
        version: 1,
        entities:
        SomeStruct.self,
        RelationStruct.self
      ),
      migrations: []
    )

load/save/delete

store.save(RelationStruct(relation: SomeStruct(intArrayField: [0, 42], stringField: "Some string")))
store.load(RelationStruct.self)
store.delete(SomeStruct.self, where: StorageFilter(keyPathString: "stringField", equal: "toDelete"))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment