WIP: documentation

This commit is contained in:
John Rommel Estropia
2017-05-10 02:00:47 +09:00
parent 19abedfa9f
commit 8ff163af30
10 changed files with 230 additions and 112 deletions

View File

@@ -32,7 +32,7 @@ import Foundation
internal extension NSEntityDescription {
@nonobjc
internal var coreStoreEntity: CoreStoreSchema.AnyEntity? {
internal var coreStoreEntity: DynamicEntity? {
get {
@@ -43,7 +43,7 @@ internal extension NSEntityDescription {
return nil
}
return CoreStoreSchema.AnyEntity(
return DynamicEntity(
type: NSClassFromString(typeName) as! CoreStoreObject.Type,
entityName: entityName,
isAbstract: isAbstract,

View File

@@ -38,10 +38,10 @@ import Foundation
public final class CSXcodeDataModelSchema: NSObject, CSDynamicSchema, CoreStoreObjectiveCType {
@objc
public required init(modelVersion: ModelVersion, modelVersionFileURL: URL) {
public required init(modelName: ModelVersion, modelVersionFileURL: URL) {
self.bridgeToSwift = XcodeDataModelSchema(
modelVersion: modelVersion,
modelName: modelName,
modelVersionFileURL: modelVersionFileURL
)
}

View File

@@ -35,7 +35,7 @@ import CoreData
public final class DataStack: Equatable {
/**
Initializes a `DataStack` from the model with the specified `modelName` in the specified `bundle`.
Convenience initializer for `DataStack` that creates a `SchemaHistory` from the model with the specified `modelName` in the specified `bundle`.
- parameter modelName: the name of the (.xcdatamodeld) model file. If not specified, the application name (CFBundleName) will be used if it exists, or "CoreData" if it the bundle name was not set (e.g. in Unit Tests).
- parameter bundle: an optional bundle to load models from. If not specified, the main bundle will be used.
@@ -53,8 +53,20 @@ public final class DataStack: Equatable {
}
/**
Initializes a `DataStack` from a `DynamicSchema` implementation.
Convenience initializer for `DataStack` that creates a `SchemaHistory` from a list of `DynamicSchema` versions.
```
CoreStore.defaultStack = DataStack(
XcodeDataModelSchema(modelName: "MyModelV1"),
CoreStoreSchema(
modelVersion: "MyModelV2",
entities: [
Entity<Animal>("Animal"),
Entity<Person>("Person")
]
),
migrationChain: ["MyModelV1", "MyModelV2"]
)
```
- parameter schema: an instance of `DynamicSchema`
- parameter otherSchema: a list of other `DynamicSchema` instances that represent present/previous/future model versions, in any order
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
@@ -67,10 +79,25 @@ public final class DataStack: Equatable {
migrationChain: migrationChain
)
)
}
/**
Initializes a `DataStack` from a `SchemaHistory` instance.
```
CoreStore.defaultStack = DataStack(
schemaHistory: SchemaHistory(
XcodeDataModelSchema(modelName: "MyModelV1"),
CoreStoreSchema(
modelVersion: "MyModelV2",
entities: [
Entity<Animal>("Animal"),
Entity<Person>("Person")
]
),
migrationChain: ["MyModelV1", "MyModelV2"]
)
)
```
- parameter schemaHistory: the `SchemaHistory` for the stack
*/
public required init(schemaHistory: SchemaHistory) {

View File

@@ -94,9 +94,9 @@ public final class CoreStoreSchema: DynamicSchema {
- parameter entities: an array of `Entity<T>` pertaining to all `CoreStoreObject` subclasses to be added to the schema version.
- parameter versionLock: an optional list of `VersionLock` hashes for each entity name in the `entities` array. If any `DynamicEntity` doesn't match its version lock hash, an assertion will be raised.
*/
public convenience init(modelVersion: ModelVersion, entities: [DynamicEntity & Hashable], versionLock: VersionLock? = nil) {
public convenience init(modelVersion: ModelVersion, entities: [DynamicEntity], versionLock: VersionLock? = nil) {
var entityConfigurations: [DynamicEntity & Hashable: Set<String>] = [:]
var entityConfigurations: [DynamicEntity: Set<String>] = [:]
for entity in entities {
entityConfigurations[entity] = []
@@ -138,15 +138,15 @@ public final class CoreStoreSchema: DynamicSchema {
- parameter entityConfigurations: a dictionary with `Entity<T>` pertaining to all `CoreStoreObject` subclasses and the corresponding list of "Configurations" they should be added to. To add an entity only to the default configuration, assign an empty set to its configurations list. Note that regardless of the set configurations, all entities will be added to the default configuration.
- parameter versionLock: an optional list of `VersionLock` hashes for each entity name in the `entities` array. If any `DynamicEntity` doesn't match its version lock hash, an assertion will be raised.
*/
public required init(modelVersion: ModelVersion, entityConfigurations: [DynamicEntity & Hashable: Set<String>], versionLock: VersionLock? = nil) {
public required init(modelVersion: ModelVersion, entityConfigurations: [DynamicEntity: Set<String>], versionLock: VersionLock? = nil) {
var actualEntitiesByConfiguration: [String: Set<AnyEntity>] = [:]
var actualEntitiesByConfiguration: [String: Set<DynamicEntity>] = [:]
for (entity, configurations) in entityConfigurations {
for configuration in configurations {
var entities: Set<AnyEntity>
if let existingEntities = actualEntitiesByConfiguration[configurations] {
var entities: Set<DynamicEntity>
if let existingEntities = actualEntitiesByConfiguration[configuration] {
entities = existingEntities
}
@@ -154,11 +154,11 @@ public final class CoreStoreSchema: DynamicSchema {
entities = []
}
entities.insert(AnyEntity(entity))
actualEntitiesByConfiguration[configurations] = entities
entities.insert(entity)
actualEntitiesByConfiguration[configuration] = entities
}
}
let allEntities = Set(actualEntitiesByConfiguration.values.joined())
let allEntities = Set(entityConfigurations.keys)
actualEntitiesByConfiguration[DataStack.defaultConfigurationName] = allEntities
CoreStore.assert(
@@ -205,7 +205,7 @@ public final class CoreStoreSchema: DynamicSchema {
return cachedRawModel
}
let rawModel = NSManagedObjectModel()
var entityDescriptionsByEntity: [AnyEntity: NSEntityDescription] = [:]
var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
for entity in self.allEntities {
let entityDescription = self.entityDescription(
@@ -234,73 +234,19 @@ public final class CoreStoreSchema: DynamicSchema {
// MARK: Internal
// MARK: - AnyEntity
internal struct AnyEntity: DynamicEntity, Hashable {
internal init(_ entity: DynamicEntity) {
self.init(
type: entity.type,
entityName: entity.entityName,
isAbstract: entity.isAbstract,
versionHashModifier: entity.versionHashModifier
)
}
internal init(type: DynamicObject.Type, entityName: String, isAbstract: Bool, versionHashModifier: String?) {
self.type = type
self.entityName = entityName
self.isAbstract = isAbstract
self.versionHashModifier = versionHashModifier
}
// MARK: Equatable
static func == (lhs: AnyEntity, rhs: AnyEntity) -> Bool {
return lhs.type == rhs.type
&& lhs.entityName == rhs.entityName
&& lhs.isAbstract == rhs.isAbstract
&& lhs.versionHashModifier == rhs.versionHashModifier
}
// MARK: Hashable
var hashValue: Int {
return ObjectIdentifier(self.type).hashValue
^ self.entityName.hashValue
^ self.isAbstract.hashValue
^ (self.versionHashModifier ?? "").hashValue
}
// MARK: DynamicEntity
internal let type: DynamicObject.Type
internal let entityName: EntityName
internal let isAbstract: Bool
internal let versionHashModifier: String?
}
// MARK: -
internal let entitiesByConfiguration: [String: Set<AnyEntity>]
internal let entitiesByConfiguration: [String: Set<DynamicEntity>]
// MARK: Private
private static let barrierQueue = DispatchQueue.concurrent("com.coreStore.coreStoreDataModelBarrierQueue")
private let allEntities: Set<AnyEntity>
private let allEntities: Set<DynamicEntity>
private var entityDescriptionsByEntity: [CoreStoreSchema.AnyEntity: NSEntityDescription] = [:]
private var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
private weak var cachedRawModel: NSManagedObjectModel?
private func entityDescription(for entity: CoreStoreSchema.AnyEntity, initializer: (CoreStoreSchema.AnyEntity) -> NSEntityDescription) -> NSEntityDescription {
private func entityDescription(for entity: DynamicEntity, initializer: (DynamicEntity) -> NSEntityDescription) -> NSEntityDescription {
if let cachedEntityDescription = self.entityDescriptionsByEntity[entity] {
@@ -311,12 +257,13 @@ public final class CoreStoreSchema: DynamicSchema {
return entityDescription
}
private static func firstPassCreateEntityDescription(from entity: AnyEntity) -> NSEntityDescription {
private static func firstPassCreateEntityDescription(from entity: DynamicEntity) -> NSEntityDescription {
let entityDescription = NSEntityDescription()
entityDescription.coreStoreEntity = entity
entityDescription.name = entity.entityName
entityDescription.isAbstract = entity.isAbstract
entityDescription.versionHashModifier = entity.versionHashModifier
entityDescription.managedObjectClassName = NSStringFromClass(NSManagedObject.self)
func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] {
@@ -360,16 +307,16 @@ public final class CoreStoreSchema: DynamicSchema {
return entityDescription
}
private static func secondPassConnectRelationshipAttributes(for entityDescriptionsByEntity: [AnyEntity: NSEntityDescription]) {
private static func secondPassConnectRelationshipAttributes(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) {
var relationshipsByNameByEntity: [AnyEntity: [String: NSRelationshipDescription]] = [:]
var relationshipsByNameByEntity: [DynamicEntity: [String: NSRelationshipDescription]] = [:]
for (entity, entityDescription) in entityDescriptionsByEntity {
relationshipsByNameByEntity[entity] = entityDescription.relationshipsByName
}
func findEntity(for type: CoreStoreObject.Type) -> AnyEntity {
func findEntity(for type: CoreStoreObject.Type) -> DynamicEntity {
var matchedEntities: Set<AnyEntity> = []
var matchedEntities: Set<DynamicEntity> = []
for (entity, _) in entityDescriptionsByEntity where entity.type == type {
matchedEntities.insert(entity)
@@ -392,7 +339,7 @@ public final class CoreStoreSchema: DynamicSchema {
}
}
func findInverseRelationshipMatching(destinationEntity: AnyEntity, destinationKeyPath: String) -> NSRelationshipDescription {
func findInverseRelationshipMatching(destinationEntity: DynamicEntity, destinationKeyPath: String) -> NSRelationshipDescription {
for case (destinationKeyPath, let relationshipDescription) in relationshipsByNameByEntity[destinationEntity]! {
@@ -451,7 +398,7 @@ public final class CoreStoreSchema: DynamicSchema {
}
}
private static func thirdPassConnectInheritanceTree(for entityDescriptionsByEntity: [AnyEntity: NSEntityDescription]) {
private static func thirdPassConnectInheritanceTree(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) {
func connectBaseEntity(mirror: Mirror, entityDescription: NSEntityDescription) {

View File

@@ -30,7 +30,7 @@ import Foundation
// MARK: - DynamicSchema
/**
`DynamicSchema` are types that provide `NSManagedObjectModel` instances for a particular model version. CoreStore currently supports the following concrete types:
`DynamicSchema` are types that provide `NSManagedObjectModel` instances for a single model version. CoreStore currently supports the following concrete types:
- `XcodeDataModelSchema`: describes models loaded from a .xcdatamodeld file.
- `LegacyXcodeDataModelSchema`: describes models loaded directly from an existing `NSManagedObjectModel`. It is not advisable to continue using this model as its metadata are not available to CoreStore.
- `CoreStoreSchema`: describes models written for `CoreStoreObject` Swift class declarations.

View File

@@ -29,8 +29,21 @@ import Foundation
// MARK: - LegacyXcodeDataModelSchema
/**
The `LegacyXcodeDataModelSchema` describes models loaded directly from an existing `NSManagedObjectModel`. It is not advisable to continue using this model as its metadata are not available to CoreStore.
*/
public final class LegacyXcodeDataModelSchema: DynamicSchema {
/**
Initializes a `LegacyXcodeDataModelSchema` from an `NSManagedObjectModel`.
```
CoreStore.defaultStack = DataStack(
LegacyXcodeDataModelSchema(modelName: "MyAppV1", model: model)
)
```
- parameter modelName: the model version, typically the file name of an *.xcdatamodeld file (without the file extension)
- parameter model: the `NSManagedObjectModel`
*/
public required init(modelName: ModelVersion, model: NSManagedObjectModel) {
self.modelVersion = modelName

View File

@@ -29,16 +29,60 @@ import Foundation
// MARK: - XcodeDataModelSchema
/**
The `XcodeDataModelSchema` describes a model version declared in a single *.xcdatamodeld file.
```
CoreStore.defaultStack = DataStack(
XcodeDataModelSchema(modelName: "MyAppV1", bundle: .main)
)
```
*/
public final class XcodeDataModelSchema: DynamicSchema {
public required init(modelVersion: ModelVersion, modelVersionFileURL: URL) {
/**
Initializes an `XcodeDataModelSchema` from an *.xcdatamodeld version name and its containing `Bundle`.
```
CoreStore.defaultStack = DataStack(
XcodeDataModelSchema(modelName: "MyAppV1", bundle: .main)
)
```
- parameter modelName: the model version, typically the file name of an *.xcdatamodeld file (without the file extension)
- parameter bundle: the `Bundle` that contains the .xcdatamodeld's "momd" file. If not specified, the `Bundle.main` will be searched.
*/
public convenience init(modelName: ModelVersion, bundle: Bundle = Bundle.main) {
guard let modelFilePath = bundle.path(forResource: modelName, ofType: "momd") else {
// For users migrating from very old Xcode versions: Old xcdatamodel files are not contained inside xcdatamodeld (with a "d"), and will thus fail this check. If that was the case, create a new xcdatamodeld file and copy all contents into the new model.
let foundModels = bundle
.paths(forResourcesOfType: "momd", inDirectory: nil)
.map({ ($0 as NSString).lastPathComponent })
CoreStore.abort("Could not find \"\(modelName).momd\" from the bundle \"\(bundle.bundleIdentifier ?? "<nil>")\". Other model files in bundle: \(foundModels.coreStoreDumpString)")
}
let modelFileURL = URL(fileURLWithPath: modelFilePath)
let fileURL = modelFileURL.appendingPathComponent("\(modelName).mom", isDirectory: false)
self.init(modelName: modelName, modelVersionFileURL: fileURL)
}
/**
Initializes an `XcodeDataModelSchema` from an *.xcdatamodeld file URL.
```
CoreStore.defaultStack = DataStack(
XcodeDataModelSchema(modelName: "MyAppV1", modelVersionFileURL: fileURL)
)
```
- parameter modelName: the model version, typically the file name of an *.xcdatamodeld file (without the file extension)
- parameter modelVersionFileURL: the file URL that points to the .xcdatamodeld's "momd" file.
*/
public required init(modelName: ModelVersion, modelVersionFileURL: URL) {
CoreStore.assert(
NSManagedObjectModel(contentsOf: modelVersionFileURL) != nil,
"Could not find the \"\(modelVersion).mom\" version file for the model at URL \"\(modelVersionFileURL)\"."
"Could not find the \"\(modelName).mom\" version file for the model at URL \"\(modelVersionFileURL)\"."
)
self.modelVersion = modelVersion
self.modelVersion = modelName
self.modelVersionFileURL = modelVersionFileURL
}

View File

@@ -28,12 +28,24 @@ import Foundation
// MARK: - DynamicObject
/**
All CoreStore's utilities are designed around `DynamicObject` instances. `NSManagedObject` and `CoreStoreObject` instances all conform to `DynamicObject`.
*/
public protocol DynamicObject: class {
/**
Used internally by CoreStore. Do not call directly.
*/
static func cs_forceCreate(entityDescription: NSEntityDescription, into context: NSManagedObjectContext, assignTo store: NSPersistentStore) -> Self
/**
Used internally by CoreStore. Do not call directly.
*/
static func cs_fromRaw(object: NSManagedObject) -> Self
/**
Used internally by CoreStore. Do not call directly.
*/
func cs_toRaw() -> NSManagedObject
}

View File

@@ -28,46 +28,99 @@ import Foundation
import ObjectiveC
// MARK: - DynamicEntity
public protocol DynamicEntity {
var type: DynamicObject.Type { get }
var entityName: EntityName { get }
var isAbstract: Bool { get }
var versionHashModifier: String? { get }
}
// MARK: Entity
public struct Entity<O: DynamicObject>: DynamicEntity, Hashable {
/**
The `Entity<O>` contains `NSEntityDescription` metadata for `CoreStoreObject` subclasses. Pass the `Entity` instances to `CoreStoreSchema` initializer.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let master = Relationship.ToOne<Person>("master")
}
class Person: CoreStoreObject {
let name = Value.Required<String>("name")
let pet = Relationship.ToOne<Animal>("pet", inverse: { $0.master })
}
CoreStore.defaultStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
Entity<Animal>("Animal"),
Entity<Person>("Person")
]
)
)
```
- SeeAlso: CoreStoreSchema
- SeeAlso: CoreStoreObject
*/
public final class Entity<O: DynamicObject>: DynamicEntity {
public init(_ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) {
/**
Initializes an `Entity`. Always provide a concrete generic type to `Entity`.
```
Entity<Animal>("Animal")
```
- parameter entityName: the `NSEntityDescription` name to use for the entity
- parameter isAbstract: set to `true` if the entity is meant to be an abstract class and can only be initialized with subclass types.
- parameter versionHashModifier: The version hash modifier for the entity. Used to mark or denote an entity as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where, for example, the structure of an entity is unchanged but the format or content of data has changed.)
*/
public convenience init(_ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) {
self.init(O.self, entityName, isAbstract: isAbstract, versionHashModifier: versionHashModifier)
}
/**
Initializes an `Entity`.
```
Entity(Animal.self, "Animal")
```
- parameter type: the `DynamicObject` type associated with the entity
- parameter entityName: the `NSEntityDescription` name to use for the entity
- parameter isAbstract: set to `true` if the entity is meant to be an abstract class and can only be initialized with subclass types.
- parameter versionHashModifier: The version hash modifier for the entity. Used to mark or denote an entity as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where, for example, the structure of an entity is unchanged but the format or content of data has changed.)
*/
public init(_ type: O.Type, _ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) {
self.type = type
self.entityName = entityName
self.isAbstract = isAbstract
self.versionHashModifier = versionHashModifier
super.init(type: type, entityName: entityName, isAbstract: isAbstract, versionHashModifier: versionHashModifier)
}
}
// MARK: - DynamicEntity
/**
Use concrete instances of `Entity<O>` in API that accept `DynamicEntity` arguments.
*/
public /*abstract*/ class DynamicEntity: Hashable {
// MARK: DynamicEntity
/**
Do not use directly.
*/
public let type: DynamicObject.Type
/**
Do not use directly.
*/
public let entityName: EntityName
/**
Do not use directly.
*/
public let isAbstract: Bool
/**
Do not use directly.
*/
public let versionHashModifier: String?
// MARK: Equatable
public static func == (lhs: Entity, rhs: Entity) -> Bool {
public static func == (lhs: DynamicEntity, rhs: DynamicEntity) -> Bool {
return lhs.type == rhs.type
&& lhs.entityName == rhs.entityName
@@ -84,4 +137,15 @@ public struct Entity<O: DynamicObject>: DynamicEntity, Hashable {
^ self.isAbstract.hashValue
^ (self.versionHashModifier ?? "").hashValue
}
// MARK: Internal
internal init(type: DynamicObject.Type, entityName: String, isAbstract: Bool = false, versionHashModifier: String?) {
self.type = type
self.entityName = entityName
self.isAbstract = isAbstract
self.versionHashModifier = versionHashModifier
}
}

View File

@@ -30,9 +30,7 @@ import Foundation
// MARK: - SchemaHistory
/**
The `SchemaHistory` encapsulates all model versions that is relevant to the data model, including past versions.
- SeeAlso: SchemaHistory.currentModelVersion
- SeeAlso: SchemaHistory.migrationChain
The `SchemaHistory` encapsulates multiple `DynamicSchema` across multiple model versions. It contains all model history and is used by the `DataStack` to
*/
public final class SchemaHistory: ExpressibleByArrayLiteral {
@@ -108,7 +106,7 @@ public final class SchemaHistory: ExpressibleByArrayLiteral {
for modelVersion in modelVersions {
let fileURL = modelFileURL.appendingPathComponent("\(modelVersion).mom", isDirectory: false)
allSchema.append(XcodeDataModelSchema(modelVersion: modelVersion, modelVersionFileURL: fileURL))
allSchema.append(XcodeDataModelSchema(modelName: modelVersion, modelVersionFileURL: fileURL))
}
self.init(
allSchema: allSchema,
@@ -117,6 +115,13 @@ public final class SchemaHistory: ExpressibleByArrayLiteral {
)
}
/**
Initializes a `SchemaHistory` with a list of `DynamicSchema` and a `MigrationChain` to describe the order of progressive migrations.
- parameter schema: a `DynamicSchema` that represents a model version
- parameter otherSchema: a list of `DynamicSchema` that represent other model versions
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
- parameter exactCurrentModelVersion: an optional string to explicitly select the current model version string. This is useful if the `DataStack` should load a non-latest model version (usually to prepare data before migration). If not provided, the current model version will be computed from the `MigrationChain`.
*/
public convenience init(_ schema: DynamicSchema, _ otherSchema: DynamicSchema..., migrationChain: MigrationChain = nil, exactCurrentModelVersion: String? = nil) {
self.init(
@@ -126,6 +131,12 @@ public final class SchemaHistory: ExpressibleByArrayLiteral {
)
}
/**
Initializes a `SchemaHistory` with a list of `DynamicSchema` and a `MigrationChain` to describe the order of progressive migrations.
- parameter allSchema: a list of `DynamicSchema` that represent model versions
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
- parameter exactCurrentModelVersion: an optional string to explicitly select the current model version string. This is useful if the `DataStack` should load a non-latest model version (usually to prepare data before migration). If not provided, the current model version will be computed from the `MigrationChain`.
*/
public required init(allSchema: [DynamicSchema], migrationChain: MigrationChain = nil, exactCurrentModelVersion: String? = nil) {
if allSchema.isEmpty {