-
released this
2024-10-31 08:37:57 +01:00 | -1 commits to develop since this release📅 Originally published on GitHub: Thu, 31 Oct 2024 08:09:31 GMT
🏷️ Git tag created: Thu, 31 Oct 2024 07:37:57 GMTI'm currently rethinking a lot of CoreStore's architectural design around Swift concurrency, and I'm planning
9.3.0to be the last major version that will support unstructured concurrency. If you have ideas or suggestions for CoreStore's direction with regards to Swift concurrency (or other Swift 6 features), feel free to post a Github issue.What's Changed
- Swift's new "Typed Throws" are now fully supported in relevant methods
- issue #509 | Xcode 16.0 beta 6 error - "'CATransaction' is unavailable in watchOS" by @DavidTiimo in https://github.com/JohnEstropia/CoreStore/pull/510
New Contributors
- @DavidTiimo made their first contribution in https://github.com/JohnEstropia/CoreStore/pull/510
Full Changelog: https://github.com/JohnEstropia/CoreStore/compare/9.2.0...9.3.0
Downloads
-
released this
2023-10-03 11:52:23 +02:00 | 7 commits to develop since this release📅 Originally published on GitHub: Mon, 24 Jun 2024 05:23:33 GMT
🏷️ Git tag created: Tue, 03 Oct 2023 09:52:23 GMTWhat's Changed
- mark designated initializer with usableFromInline by @JCSooHwanCho in https://github.com/JohnEstropia/CoreStore/pull/483
- merge operator declaration in one file by @JCSooHwanCho in https://github.com/JohnEstropia/CoreStore/pull/482
Full Changelog: https://github.com/JohnEstropia/CoreStore/compare/9.1.0...9.2.0
Downloads
-
released this
2023-06-08 04:40:05 +02:00 | 21 commits to develop since this release📅 Originally published on GitHub: Mon, 24 Jun 2024 05:21:50 GMT
🏷️ Git tag created: Thu, 08 Jun 2023 02:40:05 GMTFull Changelog: https://github.com/JohnEstropia/CoreStore/compare/9.0.0...9.1.0
Downloads
-
released this
2022-09-13 02:28:04 +02:00 | 27 commits to develop since this release📅 Originally published on GitHub: Thu, 29 Sep 2022 01:07:22 GMT
🏷️ Git tag created: Tue, 13 Sep 2022 00:28:04 GMTSwift 2.7 is bundled with Xcode 14, and CoreStore 9.0.0 will be the officially supported version from here on out.
Breaking changes:
- Removal of Objective-C support (which had been deprecated for a long time now)
- Migration to Swift 2.7 from Swift 2.4
- Bumped minimum supported version to iOS 13 from iOS 11. While this double jump had never been done in CoreStore before, we are aiming to fully utilize Combine utilities internally and to make the shift to Swift concurrency (which requires at least iOS 13) as smooth as possible.
Full Changelog: https://github.com/JohnEstropia/CoreStore/compare/8.1.0...9.0.0
Downloads
-
released this
2021-04-11 04:39:43 +02:00 | 59 commits to develop since this release📅 Originally published on GitHub: Sun, 11 Apr 2021 07:03:58 GMT
🏷️ Git tag created: Sun, 11 Apr 2021 02:39:43 GMTReactive Programming
RxSwift
RxSwift utilities are available through the RxCoreStore external module.
Combine
Combine publishers are available from the
DataStack,ListPublisher, andObjectPublisher's.reactivenamespace property.DataStack.reactiveAdding a storage through
DataStack.reactive.addStorage(_:)returns a publisher that reports aMigrationProgressenumvalue. The.migratingvalue is only emitted if the storage goes through a migration.dataStack.reactive .addStorage( SQLiteStore(fileName: "core_data.sqlite") ) .sink( receiveCompletion: { result in // ... }, receiveValue: { (progress) in print("\(round(progress.fractionCompleted * 100)) %") // 0.0 ~ 1.0 switch progress { case .migrating(let storage, let nsProgress): // ... case .finished(let storage, let migrationRequired): // ... } } ) .store(in: &cancellables)Transactions are also available as publishers through
DataStack.reactive.perform(_:), which returns a CombineFuturethat emits any type returned from the closure parameter:dataStack.reactive .perform( asynchronous: { (transaction) -> (inserted: Set<NSManagedObject>, deleted: Set<NSManagedObject>) in // ... return ( transaction.insertedObjects(), transaction.deletedObjects() ) } ) .sink( receiveCompletion: { result in // ... }, receiveValue: { value in let inserted = dataStack.fetchExisting(value0.inserted) let deleted = dataStack.fetchExisting(value0.deleted) // ... } ) .store(in: &cancellables)For importing convenience,
ImportableObjectandImportableUniqueObjectscan be imported directly throughDataStack.reactive.import[Unique]Object(_:source:)andDataStack.reactive.import[Unique]Objects(_:sourceArray:)without having to create a transaction block. In this case the publisher emits objects that are already usable directly from the main queue:dataStack.reactive .importUniqueObjects( Into<Person>(), sourceArray: [ ["name": "John"], ["name": "Bob"], ["name": "Joe"] ] ) .sink( receiveCompletion: { result in // ... }, receiveValue: { (people) in XCTAssertEqual(people?.count, 3) // ... } ) .store(in: &cancellables)ListPublisher.reactiveListPublishers can be used to emitListSnapshots through Combine usingListPublisher.reactive.snapshot(emitInitialValue:). The snapshot values are emitted in the main queue:listPublisher.reactive .snapshot(emitInitialValue: true) .sink( receiveCompletion: { result in // ... }, receiveValue: { (listSnapshot) in dataSource.apply( listSnapshot, animatingDifferences: true ) } ) .store(in: &cancellables)ObjectPublisher.reactiveObjectPublishers can be used to emitObjectSnapshots through Combine usingObjectPublisher.reactive.snapshot(emitInitialValue:). The snapshot values are emitted in the main queue:objectPublisher.reactive .snapshot(emitInitialValue: true) .sink( receiveCompletion: { result in // ... }, receiveValue: { (objectSnapshot) in tableViewCell.setObject(objectSnapshot) } ) .store(in: &tableViewCell.cancellables)SwiftUI Utilities
Observing list and object changes in SwiftUI can be done through a couple of approaches. One is by creating views that autoupdates their contents, or by declaring property wrappers that trigger view updates. Both approaches are implemented almost the same internally, but this lets you be flexible depending on the structure of your custom
Views.SwiftUI Views
CoreStore provides
Viewcontainers that automatically update their contents when data changes.ListReaderA
ListReaderobserves changes to aListPublisherand creates its content views dynamically. The builder closure receives aListSnapshotvalue that can be used to create the contents:let people: ListPublisher<Person> var body: some View { List { ListReader(self.people) { listSnapshot in ForEach(objectIn: listSnapshot) { person in // ... } } } .animation(.default) }As shown above, a typical use case is to use it together with CoreStore's
ForEachextensions.A
KeyPathcan also be optionally provided to extract specific properties of theListSnapshot:let people: ListPublisher<Person> var body: some View { ListReader(self.people, keyPath: \.count) { count in Text("Number of members: \(count)") } }ObjectReaderAn
ObjectReaderobserves changes to anObjectPublisherand creates its content views dynamically. The builder closure receives anObjectSnapshotvalue that can be used to create the contents:let person: ObjectPublisher<Person> var body: some View { ObjectReader(self.person) { objectSnapshot in // ... } .animation(.default) }A
KeyPathcan also be optionally provided to extract specific properties of theObjectSnapshot:let person: ObjectPublisher<Person> var body: some View { ObjectReader(self.person, keyPath: \.fullName) { fullName in Text("Name: \(fullName)") } }By default, an
ObjectReaderdoes not create its views wheen the object observed is deleted from the store. In those cases, theplaceholder:argument can be used to provide a customViewto display when the object is deleted:let person: ObjectPublisher<Person> var body: some View { ObjectReader( self.person, content: { objectSnapshot in // ... }, placeholder: { Text("Record not found") } ) }SwiftUI Property Wrappers
As an alternative to
ListReaderandObjectReader, CoreStore also provides property wrappers that trigger view updates when the data changes.ListStateA
@ListStateproperty exposes aListSnapshotvalue that automatically updates to the latest changes.@ListState var people: ListSnapshot<Person> init(listPublisher: ListPublisher<Person>) { self._people = .init(listPublisher) } var body: some View { List { ForEach(objectIn: self.people) { objectSnapshot in // ... } } .animation(.default) }As shown above, a typical use case is to use it together with CoreStore's
ForEachextensions.If a
ListPublisherinstance is not available yet, the fetch can be done inline by providing the fetch clauses and theDataStackinstance. By doing so the property can be declared without an initial value:@ListState( From<Person>() .sectionBy(\.age) .where(\.isMember == true) .orderBy(.ascending(\.lastName)) ) var people: ListSnapshot<Person> var body: some View { List { ForEach(sectionIn: self.people) { section in Section(header: Text(section.sectionID)) { ForEach(objectIn: section) { person in // ... } } } } .animation(.default) }For other initialization variants, refer to the ListState.swift source documentations.
ObjectStateAn
@ObjectStateproperty exposes an optionalObjectSnapshotvalue that automatically updates to the latest changes.@ObjectState var person: ObjectSnapshot<Person>? init(objectPublisher: ObjectPublisher<Person>) { self._person = .init(objectPublisher) } var body: some View { HStack { if let person = self.person { AsyncImage(person.$avatarURL) Text(person.$fullName) } else { Text("Record removed") } } }As shown above, the property's value will be
nilif the object has been deleted, so this can be used to display placeholders if needed.SwiftUI Extensions
For convenience, CoreStore provides extensions to the standard SwiftUI types.
ForEachSeveral
ForEachinitializer overloads are available. Choose depending on your input data and the expected closure data. Refer to the table below (Take note of the argument labels as they are important):Data Example Signature: ForEach(_: [ObjectSnapshot<O>])
Closure:ObjectSnapshot<O>
let array: [ObjectSnapshot<Person>]
var body: some View {
List {
ForEach(self.array) { objectSnapshot in
// ... } } }Signature: ForEach(objectIn: ListSnapshot<O>)
Closure:ObjectPublisher<O>
let listSnapshot: ListSnapshot<Person>
var body: some View {
List {
ForEach(objectIn: self.listSnapshot) { objectPublisher in
// ... } } }Signature: ForEach(objectIn: [ObjectSnapshot<O>])
Closure:ObjectPublisher<O>
let array: [ObjectSnapshot<Person>]
var body: some View {
List {
ForEach(objectIn: self.array) { objectPublisher in
// ... } } }Signature: ForEach(sectionIn: ListSnapshot<O>)
Closure:[ListSnapshot<O>.SectionInfo]
let listSnapshot: ListSnapshot<Person>
var body: some View {
List {
ForEach(sectionIn: self.listSnapshot) { sectionInfo in
// ... } } }Signature: ForEach(objectIn: ListSnapshot<O>.SectionInfo)
Closure:ObjectPublisher<O>
let listSnapshot: ListSnapshot<Person>
var body: some View {
List {
ForEach(sectionIn: self.listSnapshot) { sectionInfo in
ForEach(objectIn: sectionInfo) { objectPublisher in
// ... } } } }Downloads
-
released this
2020-09-19 08:07:43 +02:00 | 94 commits to develop since this release📅 Originally published on GitHub: Sat, 19 Sep 2020 06:38:42 GMT
🏷️ Git tag created: Sat, 19 Sep 2020 06:07:43 GMTNew Demo app
The old CoreStoreDemo app has been renamed to LegacyDemo, and a new Demo app now showcases CoreStore features through SwiftUI:
Don't worry, standard UIKit samples are also available (thanks to
UIViewControllerRepresentable)Feel free to suggest improvements to the Demo app!
Swift 5.3 / Xcode 12 / iOS 14 Support
CoreStore now compiles using Xcode 12 and Swift 5.3!
⚠️ There was a bug in Swift 5.3
propertyWrapperswhere Segmentation Faults happen during compile time. CoreStore was able to work around this issue through runtimefatalErrors, but the result is that missing required parameters for@Fieldproperties may not be caught during compile-time. The runtime checks crash if there are missing parameters, so please take care to debug your models!Downloads
-
released this
2020-06-20 10:45:14 +02:00 | 118 commits to develop since this release📅 Originally published on GitHub: Sat, 20 Jun 2020 08:48:21 GMT
🏷️ Git tag created: Sat, 20 Jun 2020 08:45:14 GMTDefault values vs. Initial values
One common mistake when assigning default values to
CoreStoreObjectproperties is to assign it a value and expect it to be evaluated whenever an object is created:// ❌ class Person: CoreStoreObject { @Field.Stored("identifier") var identifier: UUID = UUID() // Wrong! @Field.Stored("createdDate") var createdDate: Date = Date() // Wrong! }This default value will be evaluated only when the
DataStacksets up the schema, and all instances will end up having the same values. This syntax for "default values" are usually used only for actual reasonable constant values, or sentinel values such as""or0.For actual "initial values",
@Field.Storedand@Field.Codednow supports dynamic evaluation during object creation via thedynamicInitialValue:argument:// ✅ class Person: CoreStoreObject { @Field.Stored("identifier", dynamicInitialValue: { UUID() }) var identifier: UUID @Field.Stored("createdDate", dynamicInitialValue: { Date() }) var createdDate: Date }When using this feature, a "default value" should not be assigned (i.e. no
=expression).Downloads
-
released this
2020-03-27 02:32:01 +01:00 | 128 commits to develop since this release📅 Originally published on GitHub: Fri, 27 Mar 2020 03:22:27 GMT
🏷️ Git tag created: Fri, 27 Mar 2020 01:32:01 GMTMaintenance updates
- Xcode 11.4 and Swift 5.2 support
New Property Wrappers syntax
⚠️ These changes apply only to
CoreStoreObjectsubclasses, 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, andRelationship.ToManyUnorderedwill 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
VersionLockbefore converting!@Field.Stored(replacement for non "transient"Value.RequiredandValue.Optional)class Person: CoreStoreObject { @Field.Stored("title") var title: String = "Mr." @Field.Stored("nickname") var nickname: String? }⚠️ Only
Value.RequiredandValue.Optionalthat are NOT transient values can be converted toField.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 ofValue.RequiredandValue.Optional)class Animal: CoreStoreObject { @Field.Virtual( "pluralName", customGetter: { (object, field) in return object.$species.value + "s" } ) var pluralName: String @Field.Stored("species") var species: String = "" }⚠️ Only
Value.RequiredandValue.Optionalthat ARE transient values can be converted toField.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 forTransformable.RequiredandTransformable.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.RequiredandTransformable.Optionalmechanism have no safe conversion to@Field.Coded. Please use@Field.Codedonly for newly added attributes.@Field.Relationship(replacement forRelationship.ToOne,Relationship.ToManyOrdered, andRelationship.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 toT?,Relationship.ToManyOrderedmaps toArray<T>, andRelationship.ToManyUnorderedmaps toSet<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
@propertyWrapperversions will be magnitudes performant and efficient than their current implementations. CurrentlyMirrorreflection is used a lot to inject theNSManagedObjectreference into the properties. With@propertyWrappers this will be synthesized by the compiler for us. (See https://github.com/apple/swift/pull/25884) - The
@propertyWrapperversions, beingstructs, 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 andObjectPublishers by declaring them as@Field.Virtual. Note that forObjectSnapshots, 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)
Downloads
-
released this
2019-10-22 10:25:31 +02:00 | 189 commits to develop since this release📅 Originally published on GitHub: Tue, 22 Oct 2019 08:37:54 GMT
🏷️ Git tag created: Tue, 22 Oct 2019 08:25:31 GMT⚠️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 last6.3.2release if you still need Swift 5.0.Deprecations
The
CoreStore-namespaced API has been deprecated in favor ofDataStackmethod calls. If you are using the global utilities such asCoreStore.defaultStackandCoreStore.logger, a newCoreStoreDefaultsnamespace has been provided:CoreStore.defaultStack->CoreStoreDefaults.dataStackCoreStore.logger->CoreStoreDefaults.loggerCoreStore.addStorage(...)->CoreStoreDefaults.dataStack.addStorage(...)CoreStore.fetchAll(...)->CoreStoreDefaults.dataStack.fetchAll(...)- etc.
If you have been using your own properties to store
DataStackreferences, then you should not be affected by this change.New features
Backwards-portable DiffableDataSources implementation
UITableViewsandUICollectionViewsnow have a new ally:ListPublishers provide diffable snapshots that make reloading animations very easy and very safe. Say goodbye toUITableViewsandUICollectionViewsreload 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) in let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell cell.setPerson(person) return cell } )This is now the recommended method of reloading
UITableViews andUICollectionViews 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
ListPublisheris a more lightweight counterpart ofListMonitor. UnlikeListMonitor, it does not keep track of minute inserts, deletes, moves, and updates. It simply updates itssnapshotproperty which is astructstoring the list state at a specific point in time. ThisListSnapshotis then usable with theDiffableDataSourceutilities (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 onlyNSManagedObjectIDs and their sections.ObjectPublisher and ObjectSnapshot
ObjectPublisheris a more lightweight counterpart ofObjectMonitor. UnlikeObjectMonitor, it does not keep track of per-property changes. You can create anObjectPublisherfrom the object directly:let objectPublisher: ObjectPublisher<Person> = person.asPublisher(in: dataStack)or by indexing a
ListPublisher'sListSnapshot:let objectPublisher = self.listPublisher.snapshot[indexPath]The
ObjectPublisherexposes asnapshotproperty which returns anObjectSnapshot, which is a lazily generatedstructcontaining fully-copied property values.objectPublisher.addObserver(self) { [weak self] (objectPublisher) in let 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.DynamicObject func 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>? }ObjectMonitorbeing excluded in this family was intentional; its initialization is complex enough to be an API of its own.Downloads
-
released this
2019-03-30 16:29:58 +01:00 | 248 commits to develop since this release📅 Originally published on GitHub: Mon, 01 Apr 2019 03:24:09 GMT
🏷️ Git tag created: Sat, 30 Mar 2019 15:29:58 GMT- CoreStore now builds on Swift 5 and Xcode 10.2
SetupResult<T>,MigrationResult, andAsynchronousDataTransaction.Result<T>have all been converted intotypealiases forSwift.Result<T, CoreStoreError>. The benefit is we can now use the utility methods onSwift.Resultsuch asmap(),mapError(), etc. Their Objective-C counterparts (CSSetupResult, etc.) remain available and can still be used as before.- Bunch of deprecated/obsoleted stuff deleted
- CoreData iCloud support had been deprecated for a while now and CoreStore finally removes its support in this version. If you wish to continue using it please continue to use the 6.2.x versions but it will be unlikely to get bugfixes from here on out so please try to migrate your app's data as soon as possible (iOS and macOS already had this deprecated for years)
Downloads
mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-11 14:20:26 +01:00