CoreStore v7.1.0 Release Notes

Release Date: 2020-03-27 // 2 months ago
  • ⚑️ Maintenance updates

    • πŸ‘ Xcode 11.4 and Swift 5.2 support

    πŸ†• New Property Wrappers syntax

    βͺ ⚠️ These changes apply only to CoreStoreObject subclasses, notNSManagedObjects.

    🍱 ‼️ Please take note of the warnings below before migrating or else the model's hash might change.

    πŸ‘ If conversion is too risky, the current Value.Required, Value.Optional, Transformable.Required, Transformable.Optional, Relationship.ToOne, Relationship.ToManyOrdered, and Relationship.ToManyUnordered will all be supported for while so you can opt to use them as is for now.

    🍱 ‼️ If you are confident about conversion, I cannot stress this enough, but please make sure to set your schema's VersionLock before converting!

    @Field.Stored (replacement for non "transient" Value.Required and Value.Optional)

    class Person: CoreStoreObject { @Field.Stored("title") var title: String = "Mr."@Field.Stored("nickname") var nickname: String?}
    

    🍱 ⚠️ Only Value.Required and Value.Optional that are NOT transient values can be converted to Field.Stored.
    🍱 ⚠️ When converting, make sure that all parameters, including the default values, are exactly the same or else the model's hash might change.

    @Field.Virtual (replacement for "transient" versions of Value.Required andValue.Optional)

    class Animal: CoreStoreObject { @Field.Virtual( "pluralName", customGetter: { (object, field) inreturn object.$species.value + "s" } ) var pluralName: String@Field.Stored("species") var species: String = ""}
    

    🍱 ⚠️ Only Value.Required and Value.Optional that ARE transient values can be converted to Field.Virtual.
    🍱 ⚠️ When converting, make sure that all parameters, including the default values, are exactly the same or else the model's hash might change.

    πŸ‘ @Field.Coded (replacement for Transformable.Required andTransformable.Optional, with additional support for custom encoders such as JSON)

    class Person: CoreStoreObject { @Field.Coded( "bloodType", coder: { encode: { $0.toData() }, decode: { BloodType(fromData: $0) } } ) var bloodType: BloodType?}
    

    🍱 ‼️ The current Transformable.Required and Transformable.Optional mechanism have no safe conversion to @Field.Coded. Please use @Field.Coded only for newly added attributes.

    @Field.Relationship (replacement for Relationship.ToOne, Relationship.ToManyOrdered, and Relationship.ToManyUnordered)

    class Pet: CoreStoreObject { @Field.Relationship("master") var master: Person?}class Person: CoreStoreObject { @Field.Relationship("pets", inverse: \.$master) var pets: Set\<Pet\>}
    

    🍱 ⚠️ Relationship.ToOne<T> maps to T?, Relationship.ToManyOrdered maps to Array<T>, and Relationship.ToManyUnordered maps to Set<T>
    🍱 ⚠️ When converting, make sure that all parameters, including the default values, are exactly the same or else the model's hash might change.

    Usage

    Before diving into the properties themselves, note that they will effectively force you to use a different syntax for queries:

    • Before: From<Person>.where(\.title == "Mr.")
    • After: From<Person>.where(\.$title == "Mr.")

    There are a several advantages to using these Property Wrappers:

    • πŸ‘€ The @propertyWrapper versions will be magnitudes performant and efficient than their current implementations. Currently Mirror reflection is used a lot to inject the NSManagedObject reference into the properties. With @propertyWrappers this will be synthesized by the compiler for us. (See apple/swift#25884)
    • The @propertyWrapper versions, being structs, will give the compiler a lot more room for optimizations which were not possible before due to the need for mutable classes.
    • You can now add computed properties that are accessible to both ObjectSnapshots and ObjectPublishers by declaring them as @Field.Virtual. Note that for ObjectSnapshots, the computed values are evaluated only once during creation and are not recomputed afterwards.

    The only disadvantage will be:

    • ⚑️ You need to update your code by hand to migrate to the new @propertyWrappers
      (But the legacy ones will remain available for quite a while, so while it is recommended to migrate soon, no need to panic)

Previous changes from v7.0.0

  • ⚑️ ⚠️This update will break current code. Make sure to read the changes below:

    πŸ’₯ Breaking Changes

    πŸš€ Starting version 7.0.0, CoreStore will be using a lot of Swift 5.1 features, both internally and in its public API. You can keep using the last 6.3.2 release if you still need Swift 5.0.

    πŸ—„ Deprecations

    βͺ The CoreStore-namespaced API has been deprecated in favor of DataStack method calls. If you are using the global utilities such as CoreStore.defaultStack and CoreStore.logger, a new CoreStoreDefaults namespace has been provided:

    • βͺ CoreStore.defaultStack -> CoreStoreDefaults.dataStack
    • βͺ CoreStore.logger -> CoreStoreDefaults.logger
    • βͺ CoreStore.addStorage(...) -> CoreStoreDefaults.dataStack.addStorage(...)
    • βͺ CoreStore.fetchAll(...) -> CoreStoreDefaults.dataStack.fetchAll(...)
    • etc.

    If you have been using your own properties to store DataStack references, then you should not be affected by this change.

    πŸ†• New features

    Backwards-portable DiffableDataSources implementation

    UITableViews and UICollectionViews now have a new ally: ListPublishers provide diffable snapshots that make reloading animations very easy and very safe. Say goodbye to UITableViews and UICollectionViews reload errors!

    🍎 DiffableDataSource.CollectionView (iOS and macOS) and DiffableDataSource.TableView (iOS)

    self.dataSource = DiffableDataSource.CollectionView\<Person\>( collectionView: self.collectionView, dataStack: CoreStoreDefaults.dataStack, cellProvider: { (collectionView, indexPath, person) inlet cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell cell.setPerson(person) return cell } )
    

    ⚑️ This is now the recommended method of reloading UITableViews and UICollectionViews because it uses list diffing to update your list views. This means that it is a lot less prone to cause layout errors.

    ListPublisher and ListSnapshot

    ⚑️ ListPublisher is a more lightweight counterpart of ListMonitor. Unlike ListMonitor, it does not keep track of minute inserts, deletes, moves, and updates. It simply updates its snapshot property which is a struct storing the list state at a specific point in time. This ListSnapshot is then usable with the DiffableDataSource utilities (See section above).

    self.listPublisher = dataStack.listPublisher( From\<Person\>() .sectionBy(\.age") { "Age \($0)" } // sections are optional .where(\.title == "Engineer") .orderBy(.ascending(\.lastName)))self.listPublisher.addObserver(self) { [weak self] (listPublisher) in self?.dataSource?.apply( listPublisher.snapshot, animatingDifferences: true )}
    

    ListSnapshots store only NSManagedObjectIDs and their sections.

    ObjectPublisher and ObjectSnapshot

    ObjectPublisher is a more lightweight counterpart of ObjectMonitor. Unlike ObjectMonitor, it does not keep track of per-property changes. You can create an ObjectPublisher from the object directly:

    let objectPublisher: ObjectPublisher\<Person\> = person.asPublisher(in: dataStack)
    

    or by indexing a ListPublisher's ListSnapshot:

    let objectPublisher = self.listPublisher.snapshot[indexPath]
    

    The ObjectPublisher exposes a snapshot property which returns an ObjectSnapshot, which is a lazily generated struct containing fully-copied property values.

    objectPublisher.addObserver(self) { [weak self] (objectPublisher) inlet snapshot: ObjectSnapshot\<Person\> = objectPublisher.snapshot// handle changes}
    

    This snapshot is completely thread-safe, and any mutations to it will not affect the actual object.

    Intent-based Object representations

    βͺ CoreStore is slowly moving to abstract object utilities based on usage intent.
    βͺ NSManageObject',CoreStoreObject,ObjectPublisher, andObjectSnapshotall conform to theObjectRepresentation` protocol, which allows conversion of each type to another:

    public protocol ObjectRepresentation { associatedtype ObjectType : CoreStore.DynamicObjectfunc objectID() -\> ObjectType.ObjectID func asPublisher(in dataStack: DataStack) -\> ObjectPublisher\<ObjectType\> func asReadOnly(in dataStack: DataStack) -\> ObjectType?func asEditable(in transaction: BaseDataTransaction) -\> ObjectType?func asSnapshot(in dataStack: DataStack) -\> ObjectSnapshot\<ObjectType\>?func asSnapshot(in transaction: BaseDataTransaction) -\> ObjectSnapshot\<ObjectType\>?}
    

    ObjectMonitor being excluded in this family was intentional; its initialization is complex enough to be an API of its own.