Merge branch 'master' into prototype/containers

This commit is contained in:
John Estropia
2021-04-18 09:01:41 +09:00
59 changed files with 1160 additions and 939 deletions

View File

@@ -30,7 +30,6 @@ import CoreData
// MARK: - CSDataStack
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@available(macOS 10.12, *)
extension CSDataStack {
/**

View File

@@ -34,7 +34,6 @@ import CoreData
- SeeAlso: `ListMonitor`
*/
@available(macOS 10.12, *)
@objc
public final class CSListMonitor: NSObject {
@@ -546,7 +545,6 @@ public final class CSListMonitor: NSObject {
// MARK: - ListMonitor
@available(macOS 10.12, *)
extension ListMonitor where ListMonitor.ObjectType: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -40,7 +40,6 @@ import CoreData
- SeeAlso: `ListObserver`
*/
@available(macOS 10.12, *)
@objc
public protocol CSListObserver: AnyObject {
@@ -91,7 +90,6 @@ public protocol CSListObserver: AnyObject {
- SeeAlso: `ListObjectObserver`
*/
@available(macOS 10.12, *)
@objc
public protocol CSListObjectObserver: CSListObserver {
@@ -152,7 +150,6 @@ public protocol CSListObjectObserver: CSListObserver {
- SeeAlso: `ListSectionObserver`
*/
@available(macOS 10.12, *)
@objc
public protocol CSListSectionObserver: CSListObjectObserver {

View File

@@ -34,7 +34,6 @@ import CoreData
- SeeAlso: `ObjectMonitor`
*/
@available(macOS 10.12, *)
@objc
public final class CSObjectMonitor: NSObject {
@@ -139,7 +138,6 @@ public final class CSObjectMonitor: NSObject {
// MARK: - ObjectMonitor
@available(macOS 10.12, *)
extension ObjectMonitor where ObjectMonitor.ObjectType: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -38,7 +38,6 @@ import CoreData
- SeeAlso: `ObjectObserver`
*/
@available(macOS 10.12, *)
@objc
public protocol CSObjectObserver: AnyObject {

View File

@@ -34,7 +34,6 @@ import CoreData
- SeeAlso: `SectionBy`
*/
@available(macOS 10.12, *)
@objc
public final class CSSectionBy: NSObject {
@@ -91,7 +90,6 @@ public final class CSSectionBy: NSObject {
// MARK: - SectionBy
@available(macOS 10.12, *)
extension SectionBy {
// MARK: CoreStoreSwiftType

View File

@@ -359,7 +359,6 @@ extension UnsafeDataModelSchema: CustomDebugStringConvertible, CoreStoreDebugStr
// MARK: - ListMonitor
@available(macOS 10.12, *)
fileprivate struct CoreStoreFetchedSectionInfoWrapper: CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -404,7 +403,6 @@ fileprivate struct CoreStoreFetchedSectionInfoWrapper: CoreStoreDebugStringConve
private let numberOfObjects: Int
}
@available(macOS 10.12, *)
extension ListMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -431,7 +429,6 @@ extension ListMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvert
// MARK: - ListPublisher
@available(macOS 10.12, *)
extension ListPublisher: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -614,7 +611,6 @@ extension MigrationType: CoreStoreDebugStringConvertible {
// MARK: - ObjectMonitor
@available(macOS 10.12, *)
extension ObjectMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -738,7 +734,6 @@ extension PartialObject: CustomDebugStringConvertible, CoreStoreDebugStringConve
// MARK: - SectionBy
@available(macOS 10.12, *)
extension SectionBy: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -1259,33 +1254,20 @@ extension NSEntityDescription: CoreStoreDebugStringConvertible {
public var coreStoreDumpString: String {
var info: DumpInfo = [
return createFormattedString(
"(", ")",
("managedObjectClassName", self.managedObjectClassName!),
("name", self.name as Any),
("indexes", self.indexes),
("isAbstract", self.isAbstract),
("superentity?.name", self.superentity?.name as Any),
("subentities", self.subentities.map({ $0.name })),
("properties", self.properties),
("uniquenessConstraints", self.uniquenessConstraints),
("userInfo", self.userInfo as Any),
("versionHash", self.versionHash),
("versionHashModifier", self.versionHashModifier as Any),
("renamingIdentifier", self.renamingIdentifier as Any)
]
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
info.append(("indexes", self.indexes))
}
else {
info.append(("compoundIndexes", self.compoundIndexes))
}
if #available(macOS 10.11, iOS 9.0, *) {
info.append(("uniquenessConstraints", self.uniquenessConstraints))
}
return createFormattedString(
"(", ")",
info
)
}
}

View File

@@ -564,18 +564,14 @@ public final class CoreStoreSchema: DynamicSchema {
)
}
for (entity, entityDescription) in entityDescriptionsByEntity {
if entity.uniqueConstraints.contains(where: { !$0.isEmpty }) {
if #available(macOS 10.11, iOS 9.0, *) {
let uniqueConstraints = entity.uniqueConstraints.filter({ !$0.isEmpty })
if !uniqueConstraints.isEmpty {
Internals.assert(
entityDescription.superentity == nil,
"Uniqueness constraints must be defined at the highest level possible."
)
entityDescription.uniquenessConstraints = entity.uniqueConstraints.map { $0.map { $0 as NSString } }
}
Internals.assert(
entityDescription.superentity == nil,
"Uniqueness constraints must be defined at the highest level possible."
)
entityDescription.uniquenessConstraints = entity.uniqueConstraints.map { $0.map { $0 as NSString } }
}
guard !entity.indexes.isEmpty else {
@@ -586,31 +582,18 @@ public final class CoreStoreSchema: DynamicSchema {
entityDescription.coreStoreEntity = entity // reserialize
}
let attributesByName = entityDescription.attributesByName
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
entityDescription.indexes = entity.indexes.map { (compoundIndexes) in
entityDescription.indexes = entity.indexes.map { (compoundIndexes) in
return NSFetchIndexDescription.init(
name: "_CoreStoreSchema_indexes_\(entityDescription.name!)_\(compoundIndexes.joined(separator: "-"))",
elements: compoundIndexes.map { (keyPath) in
return NSFetchIndexElementDescription(
property: attributesByName[keyPath]!,
collationType: .binary
)
}
)
}
}
else {
entityDescription.compoundIndexes = entity.indexes.map { (compoundIndexes) in
return compoundIndexes.map { (keyPath) in
return NSFetchIndexDescription.init(
name: "_CoreStoreSchema_indexes_\(entityDescription.name!)_\(compoundIndexes.joined(separator: "-"))",
elements: compoundIndexes.map { (keyPath) in
return attributesByName[keyPath]!
return NSFetchIndexElementDescription(
property: attributesByName[keyPath]!,
collationType: .binary
)
}
}
)
}
}
}

View File

@@ -613,16 +613,7 @@ extension DataStack {
}
}
let fileManager = FileManager.default
let systemTemporaryDirectoryURL: URL
if #available(macOS 10.12, iOS 10.0, *) {
systemTemporaryDirectoryURL = fileManager.temporaryDirectory
}
else {
systemTemporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
}
let temporaryDirectoryURL = systemTemporaryDirectoryURL
let temporaryDirectoryURL = fileManager.temporaryDirectory
.appendingPathComponent(Bundle.main.bundleIdentifier ?? "com.CoreStore.DataStack")
.appendingPathComponent(ProcessInfo().globallyUniqueString)

View File

@@ -29,7 +29,6 @@ import CoreData
// MARK: - DataStack
@available(macOS 10.12, *)
extension DataStack {
/**

View File

@@ -75,6 +75,78 @@ extension DataStack.ReactiveNamespace {
// MARK: Public
/**
Reactive extension for `CoreStore.DataStack`'s `addStorage(...)` API. Asynchronously adds a `StorageInterface` to the stack.
```
dataStack.reactive
.addStorage(
InMemoryStore(configuration: "Config1")
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { storage in
// ...
}
)
.store(in: &cancellables)
```
- parameter storage: the storage
- returns: A `Future` that emits a `StorageInterface` instance added to the `DataStack`. Note that the `StorageInterface` event value may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration.
*/
public func addStorage<T: StorageInterface>(_ storage: T) -> Future<T, CoreStoreError> {
return .init { (promise) in
self.base.addStorage(
storage,
completion: { (result) in
switch result {
case .success(let storage):
promise(.success(storage))
case .failure(let error):
promise(.failure(error))
}
}
)
}
}
/**
Reactive extension for `CoreStore.DataStack`'s `addStorage(...)` API. Asynchronously adds a `LocalStorage` to the stack. Migrations are also initiated by default. The event emits `DataStack.AddStoragePublisher.MigrationProgress` `enum` values.
```
dataStack.reactive
.addStorage(
SQLiteStore(
fileName: "core_data.sqlite",
configuration: "Config1"
)
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (progress) in
print("\(round(progress.fractionCompleted * 100)) %") // 0.0 ~ 1.0
}
)
.store(in: &cancellables)
```
- parameter storage: the local storage
- returns: A `DataStack.AddStoragePublisher` that emits a `DataStack.AddStoragePublisher.MigrationProgress` value with metadata for migration progress. Note that the `LocalStorage` event value may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
*/
public func addStorage<T: LocalStorage>(_ storage: T) -> DataStack.AddStoragePublisher<T> {
return .init(
dataStack: self.base,
storage: storage
)
}
/**
Reactive extension for `CoreStore.DataStack`'s `importObject(...)` API. Creates an `ImportableObject` by importing from the specified import source. The event value will be the object instance correctly associated for the `DataStack`.
```

View File

@@ -0,0 +1,239 @@
//
// DataStack.AddStoragePublisher.swift
// CoreStore
//
// Copyright © 2021 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(Combine)
import Combine
import CoreData
// MARK: - DataStack
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension DataStack {
// MARK: - AddStoragePublisher
/**
A `Publisher` that emits a `ListSnapshot` whenever changes occur in the `ListPublisher`.
- SeeAlso: DataStack.reactive.addStorage(_:)
*/
public struct AddStoragePublisher<Storage: LocalStorage>: Publisher {
// MARK: Internal
internal let dataStack: DataStack
internal let storage: Storage
// MARK: Publisher
public typealias Output = MigrationProgress
public typealias Failure = CoreStoreError
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
subscriber.receive(
subscription: AddStorageSubscription(
dataStack: self.dataStack,
storage: self.storage,
subscriber: subscriber
)
)
}
// MARK: - MigrationProgress
/**
A `MigrationProgress` contains info on a `LocalStorage`'s setup progress.
- SeeAlso: DataStack.reactive.addStorage(_:)
*/
public enum MigrationProgress {
/**
The `LocalStorage` is currently being migrated
*/
case migrating(storage: Storage, progressObject: Progress)
/**
The `LocalStorage` has been added to the `DataStack` and is ready for reading and writing
*/
case finished(storage: Storage, migrationRequired: Bool)
/**
The fraction of the overall work completed by the migration. Returns a value between 0.0 and 1.0, inclusive.
*/
public var fractionCompleted: Double {
switch self {
case .migrating(_, let progressObject):
return progressObject.fractionCompleted
case .finished:
return 1
}
}
/**
Returns `true` if the storage was successfully added to the stack, `false` otherwise.
*/
public var isCompleted: Bool {
switch self {
case .migrating:
return false
case .finished:
return true
}
}
}
// MARK: - AddStorageSubscriber
fileprivate final class AddStorageSubscriber: Subscriber {
// MARK: Subscriber
typealias Failure = CoreStoreError
func receive(subscription: Subscription) {
subscription.request(.unlimited)
}
func receive(_ input: Output) -> Subscribers.Demand {
return .unlimited
}
func receive(completion: Subscribers.Completion<Failure>) {}
}
// MARK: - AddStorageSubscription
fileprivate final class AddStorageSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == CoreStoreError {
// MARK: FilePrivate
fileprivate init(
dataStack: DataStack,
storage: Storage,
subscriber: S
) {
self.dataStack = dataStack
self.storage = storage
self.subscriber = subscriber
}
// MARK: Subscription
func request(_ demand: Subscribers.Demand) {
guard demand > 0 else {
return
}
var progress: Progress? = nil
progress = self.dataStack.addStorage(
self.storage,
completion: { [weak self] result in
progress?.setProgressHandler(nil)
guard
let self = self,
let subscriber = self.subscriber
else {
return
}
switch result {
case .success(let storage):
_ = subscriber.receive(
.finished(
storage: storage,
migrationRequired: progress != nil
)
)
subscriber.receive(
completion: .finished
)
case .failure(let error):
subscriber.receive(
completion: .failure(error)
)
}
}
)
if let progress = progress {
progress.cs_setProgressHandler { [weak self] progress in
guard
let self = self,
let subscriber = self.subscriber
else {
return
}
_ = subscriber.receive(
.migrating(
storage: self.storage,
progressObject: progress
)
)
}
}
}
// MARK: Cancellable
func cancel() {
self.subscriber = nil
}
// MARK: Private
private let dataStack: DataStack
private let storage: Storage
private var subscriber: S?
}
}
}
#endif

View File

@@ -247,16 +247,7 @@ extension DiffableDataSource {
return
}
if #available(iOS 11.0, tvOS 11.0, *) {
base.performBatchUpdates(updates)
}
else {
base.beginUpdates()
updates()
base.endUpdates()
}
base.performBatchUpdates(updates)
}
public func reloadData() {

View File

@@ -70,7 +70,6 @@ public final class Entity<O: CoreStoreObject>: DynamicEntity {
- parameter indexes: the compound indexes for the entity as an array of arrays. The arrays contained in the returned array contain `KeyPath`s to properties of the entity.
- parameter uniqueConstraints: sets uniqueness constraints for the entity. A uniqueness constraint is a set of one or more `KeyPath`s whose value must be unique over the set of instances of that entity. This value forms part of the entity's version hash. Uniqueness constraint violations can be computationally expensive to handle. It is highly suggested that there be only one uniqueness constraint per entity hierarchy. Uniqueness constraints must be defined at the highest level possible, and CoreStore will raise an assertion failure if unique constraints are added to a sub entity.
*/
@available(macOS 10.11, *)
public convenience init(_ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil, indexes: [[PartialKeyPath<O>]] = [], uniqueConstraints: [[PartialKeyPath<O>]]) {
self.init(
@@ -116,7 +115,6 @@ public final class Entity<O: CoreStoreObject>: DynamicEntity {
- parameter indexes: the compound indexes for the entity as an array of arrays. The arrays contained in the returned array contain KeyPath's to properties of the entity.
- parameter uniqueConstraints: sets uniqueness constraints for the entity. A uniqueness constraint is a set of one or more `KeyPath`s whose value must be unique over the set of instances of that entity. This value forms part of the entity's version hash. Uniqueness constraint violations can be computationally expensive to handle. It is highly suggested that there be only one uniqueness constraint per entity hierarchy. Uniqueness constraints must be defined at the highest level possible, and CoreStore will raise an assertion failure if unique constraints are added to a sub entity.
*/
@available(macOS 10.11, *)
public init(_ type: O.Type, _ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil, indexes: [[PartialKeyPath<O>]] = [], uniqueConstraints: [[PartialKeyPath<O>]]) {
let meta = O.meta

View File

@@ -47,17 +47,10 @@ extension FieldCoders {
return nil
}
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
return try! NSKeyedArchiver.archivedData(
withRootObject: fieldValue,
requiringSecureCoding: self.requiresSecureCoding
)
}
else {
return NSKeyedArchiver.archivedData(withRootObject: fieldValue)
}
return try! NSKeyedArchiver.archivedData(
withRootObject: fieldValue,
requiringSecureCoding: self.requiresSecureCoding
)
}
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
@@ -66,14 +59,10 @@ extension FieldCoders {
return nil
}
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
return try! NSKeyedUnarchiver.unarchivedObject(ofClass: FieldStoredValue.self, from: data)
}
else {
return NSKeyedUnarchiver.unarchiveObject(with: data) as! FieldStoredValue?
}
return try! NSKeyedUnarchiver.unarchivedObject(
ofClass: FieldStoredValue.self,
from: data
)
}

View File

@@ -35,11 +35,40 @@ import SwiftUI
extension ForEach where Content: View {
// MARK: Public
/**
Creates an instance that creates views for each object in a collection of `ObjectSnapshot`s. The objects' `NSManagedObjectID` are used as the identifier
```
let people: [ObjectSnapshot<Person>]
var body: some View {
List {
ForEach(self.people) { person in
ProfileView(person)
}
}
.animation(.default)
}
```
- parameter objectSnapshots: The collection of `ObjectSnapshot`s that the `ForEach` instance uses to create views dynamically
- parameter content: The view builder that receives an `ObjectPublisher` instance and creates views dynamically.
*/
public init<O: DynamicObject>(
_ objectSnapshots: Data,
@ViewBuilder content: @escaping (ObjectSnapshot<O>) -> Content
) where Data.Element == ObjectSnapshot<O>, ID == O.ObjectID {
self.init(objectSnapshots, id: \.cs_objectID, content: content)
}
/**
Creates an instance that creates views for each object in a `ListSnapshot`.
```
@LiveList
@ListState
var people: ListSnapshot<Person>
var body: some View {
@@ -98,7 +127,7 @@ extension ForEach where Content: View {
/**
Creates an instance that creates views for `ListSnapshot` sections.
```
@LiveList
@ListState
var people: ListSnapshot<Person>
var body: some View {
@@ -135,7 +164,7 @@ extension ForEach where Content: View {
/**
Creates an instance that creates views for each object in a `ListSnapshot.SectionInfo`.
```
@LiveList
@ListState
var people: ListSnapshot<Person>
var body: some View {

View File

@@ -221,7 +221,6 @@ extension From {
- parameter clause: the `SectionBy` to be used by the `ListMonitor`
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy(_ clause: SectionBy<O>) -> SectionMonitorChainBuilder<O> {
return .init(
@@ -237,7 +236,6 @@ extension From {
- parameter sectionKeyPath: the key path to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy(_ sectionKeyPath: KeyPathString) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(sectionKeyPath, sectionIndexTransformer: { _ in nil })
@@ -251,7 +249,6 @@ extension From {
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy(
_ sectionKeyPath: KeyPathString,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -284,7 +281,6 @@ extension From {
// MARK: Deprecated
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
@available(macOS 10.12, *)
public func sectionBy(
_ sectionKeyPath: KeyPathString,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -319,7 +315,6 @@ extension From where O: NSManagedObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, T>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
@@ -336,7 +331,6 @@ extension From where O: NSManagedObject {
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, T>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -352,7 +346,6 @@ extension From where O: NSManagedObject {
// MARK: Deprecated
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, T>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -431,7 +424,6 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
@@ -446,7 +438,6 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
@@ -461,7 +452,6 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
@@ -476,7 +466,6 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
@@ -491,7 +480,6 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
@@ -506,7 +494,6 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
@@ -521,7 +508,6 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
@@ -538,7 +524,6 @@ extension From where O: CoreStoreObject {
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -558,7 +543,6 @@ extension From where O: CoreStoreObject {
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -578,7 +562,6 @@ extension From where O: CoreStoreObject {
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -598,7 +581,6 @@ extension From where O: CoreStoreObject {
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -618,7 +600,6 @@ extension From where O: CoreStoreObject {
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -638,7 +619,6 @@ extension From where O: CoreStoreObject {
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -658,7 +638,6 @@ extension From where O: CoreStoreObject {
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -674,7 +653,6 @@ extension From where O: CoreStoreObject {
// MARK: Deprecated
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -687,7 +665,6 @@ extension From where O: CoreStoreObject {
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -700,7 +677,6 @@ extension From where O: CoreStoreObject {
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -713,7 +689,6 @@ extension From where O: CoreStoreObject {
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -726,7 +701,6 @@ extension From where O: CoreStoreObject {
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -739,7 +713,6 @@ extension From where O: CoreStoreObject {
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -752,7 +725,6 @@ extension From where O: CoreStoreObject {
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
@available(macOS 10.12, *)
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
@@ -1224,7 +1196,6 @@ extension QueryChainBuilder where O: CoreStoreObject {
// MARK: - SectionMonitorChainBuilder
@available(macOS 10.12, *)
extension SectionMonitorChainBuilder {
/**
@@ -1376,7 +1347,6 @@ extension SectionMonitorChainBuilder {
// MARK: - SectionMonitorChainBuilder where O: CoreStoreObject
@available(macOS 10.12, *)
extension SectionMonitorChainBuilder where O: CoreStoreObject {
/**

View File

@@ -77,27 +77,42 @@ extension Internals {
let transformerName = self.transformerName
if #available(iOS 12.0, tvOS 12.0, watchOS 5.0, macOS 10.14, *) {
if transformerName == .secureUnarchiveFromDataTransformerName {
switch transformerName {
case .secureUnarchiveFromDataTransformerName,
.isNotNilTransformerName,
.isNilTransformerName,
.negateBooleanTransformerName:
return
case let transformerName:
Self.cachedCoders[transformerName] = self
Foundation.ValueTransformer.setValueTransformer(
self.transformer,
forName: transformerName
)
}
}
switch transformerName {
else {
switch transformerName {
case .keyedUnarchiveFromDataTransformerName,
.unarchiveFromDataTransformerName,
.isNotNilTransformerName,
.isNilTransformerName,
.negateBooleanTransformerName:
return
case .keyedUnarchiveFromDataTransformerName,
.unarchiveFromDataTransformerName,
.isNotNilTransformerName,
.isNilTransformerName,
.negateBooleanTransformerName:
return
case let transformerName:
Self.cachedCoders[transformerName] = self
case let transformerName:
Self.cachedCoders[transformerName] = self
Foundation.ValueTransformer.setValueTransformer(
self.transformer,
forName: transformerName
)
Foundation.ValueTransformer.setValueTransformer(
self.transformer,
forName: transformerName
)
}
}
}

View File

@@ -68,22 +68,8 @@ extension Internals {
}
set {
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
self.copiedAffectedStores = (newValue as NSArray?)?.copy() as! NSArray?
super.affectedStores = newValue
return
}
// Bugfix for NSFetchRequest messing up memory management for `affectedStores`
// http://stackoverflow.com/questions/14396375/nsfetchedresultscontroller-crashes-in-ios-6-if-affectedstores-is-specified
if let releaseArray = self.releaseArray {
releaseArray.release()
self.releaseArray = nil
}
self.copiedAffectedStores = (newValue as NSArray?)?.copy() as! NSArray?
super.affectedStores = newValue
self.releaseArray = (super.affectedStores as NSArray?).map(Unmanaged<NSArray>.passRetained(_:))
}
}

View File

@@ -33,7 +33,6 @@ extension Internals {
// MARK: - CoreStoreFetchedResultsController
@available(macOS 10.12, *)
internal final class CoreStoreFetchedResultsController: NSFetchedResultsController<NSManagedObject> {
// MARK: Internal

View File

@@ -29,7 +29,6 @@ import CoreData
// MARK: - FetchedResultsControllerHandler
@available(macOS 10.12, *)
internal protocol FetchedResultsControllerHandler: AnyObject {
var sectionIndexTransformer: (_ sectionName: KeyPathString?) -> String? { get }
@@ -50,7 +49,6 @@ extension Internals {
// MARK: - FetchedResultsControllerDelegate
@available(macOS 10.12, *)
internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
// MARK: Internal
@@ -90,14 +88,6 @@ extension Internals {
return
}
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {}
else {
self.deletedSections = []
self.insertedSections = []
}
self.handler?.controllerWillChangeContent(controller)
}
@@ -123,102 +113,11 @@ extension Internals {
return
}
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: type,
newIndexPath: newIndexPath
)
return
}
guard var actualType = NSFetchedResultsChangeType(rawValue: type.rawValue) else {
// This fix is for a bug where iOS passes 0 for NSFetchedResultsChangeType, but this is not a valid enum case.
// Swift will then always execute the first case of the switch causing strange behaviour.
// https://forums.developer.apple.com/thread/12184#31850
return
}
// This whole dance is a workaround for a nasty bug introduced in XCode 7 targeted at iOS 8 devices
// http://stackoverflow.com/questions/31383760/ios-9-attempt-to-delete-and-reload-the-same-index-path/31384014#31384014
// https://forums.developer.apple.com/message/9998#9998
// https://forums.developer.apple.com/message/31849#31849
if case .update = actualType,
indexPath != nil,
newIndexPath != nil {
actualType = .move
}
switch actualType {
case .update:
guard let section = indexPath?[0] else {
return
}
if self.deletedSections.contains(section)
|| self.insertedSections.contains(section) {
return
}
case .move:
guard let indexPath = indexPath, let newIndexPath = newIndexPath else {
return
}
guard indexPath == newIndexPath else {
break
}
if self.insertedSections.contains(indexPath[0]) {
// Observers that handle the .Move change are advised to delete then reinsert the object instead of just moving. This is especially true when indexPath and newIndexPath are equal. For example, calling tableView.moveRowAtIndexPath(_:toIndexPath) when both indexPaths are the same will crash the tableView.
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: .move,
newIndexPath: newIndexPath
)
return
}
if self.deletedSections.contains(indexPath[0]) {
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: nil,
forChangeType: .insert,
newIndexPath: indexPath
)
return
}
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: .update,
newIndexPath: nil
)
return
default:
break
}
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: actualType,
forChangeType: type,
newIndexPath: newIndexPath
)
}
@@ -230,18 +129,6 @@ extension Internals {
return
}
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {}
else {
switch type {
case .delete: self.deletedSections.insert(sectionIndex)
case .insert: self.insertedSections.insert(sectionIndex)
default: break
}
}
self.handler?.controller(
controller,
didChangeSection: sectionInfo,
@@ -255,14 +142,5 @@ extension Internals {
return self.handler?.sectionIndexTransformer(sectionName)
}
// MARK: Private
@nonobjc
private var deletedSections = Set<Int>()
@nonobjc
private var insertedSections = Set<Int>()
}
}

View File

@@ -66,7 +66,6 @@ import CoreData
```
In the example above, both `person1` and `person2` will contain the object at section=2, index=3.
*/
@available(macOS 10.12, *)
public final class ListMonitor<O: DynamicObject>: Hashable {
// MARK: Public (Accessors)
@@ -1178,7 +1177,6 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
// MARK: - ListMonitor where O: NSManagedObject
@available(macOS 10.12, *)
extension ListMonitor where O: NSManagedObject {
/**
@@ -1236,7 +1234,6 @@ extension ListMonitor where O: NSManagedObject {
// MARK: - ListMonitor where O: CoreStoreObject
@available(macOS 10.12, *)
extension ListMonitor where O: CoreStoreObject {
/**
@@ -1288,7 +1285,6 @@ extension ListMonitor where O: CoreStoreObject {
// MARK: - ListMonitor: FetchedResultsControllerHandler
@available(macOS 10.12, *)
extension ListMonitor: FetchedResultsControllerHandler {
// MARK: FetchedResultsControllerHandler
@@ -1402,7 +1398,6 @@ extension ListMonitor: FetchedResultsControllerHandler {
// MARK: - Notification Keys
@available(macOS 10.12, *)
extension Notification.Name {
fileprivate static let listMonitorWillChangeList = Notification.Name(rawValue: "listMonitorWillChangeList")

View File

@@ -39,7 +39,6 @@ import CoreData
monitor.addObserver(self)
```
*/
@available(macOS 10.12, *)
public protocol ListObserver: AnyObject {
/**
@@ -82,7 +81,6 @@ public protocol ListObserver: AnyObject {
// MARK: - ListObserver (Default Implementations)
@available(macOS 10.12, *)
extension ListObserver {
public func listMonitorWillChange(_ monitor: ListMonitor<ListEntityType>) { }
@@ -103,7 +101,6 @@ extension ListObserver {
monitor.addObserver(self)
```
*/
@available(macOS 10.12, *)
public protocol ListObjectObserver: ListObserver {
/**
@@ -151,7 +148,6 @@ public protocol ListObjectObserver: ListObserver {
// MARK: - ListObjectObserver (Default Implementations)
@available(macOS 10.12, *)
extension ListObjectObserver {
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: IndexPath) { }
@@ -177,7 +173,6 @@ extension ListObjectObserver {
monitor.addObserver(self)
```
*/
@available(macOS 10.12, *)
public protocol ListSectionObserver: ListObjectObserver {
/**
@@ -204,7 +199,6 @@ public protocol ListSectionObserver: ListObjectObserver {
// MARK: - ListSectionObserver (Default Implementations)
@available(macOS 10.12, *)
extension ListSectionObserver {
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { }

View File

@@ -37,6 +37,8 @@ extension ListPublisher {
/**
A `Publisher` that emits a `ListSnapshot` whenever changes occur in the `ListPublisher`.
- SeeAlso: ListPublisher.reactive.snapshot(emitInitialValue:)
*/
public struct SnapshotPublisher: Publisher {

View File

@@ -25,16 +25,6 @@
import CoreData
#if canImport(Combine)
import Combine
#endif
#if canImport(SwiftUI)
import SwiftUI
#endif
// MARK: - ListPublisher
@@ -64,7 +54,6 @@ import SwiftUI
```
All access to the `ListPublisher` items should be done via its `snapshot` value, which is a `struct` of type `ListSnapshot<O>`. `ListSnapshot`s are also designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s. For detailed examples, refer to the documentation for `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter`.
*/
@available(macOS 10.12, *)
public final class ListPublisher<O: DynamicObject>: Hashable {
// MARK: Public (Accessors)
@@ -89,13 +78,8 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
*/
public fileprivate(set) var snapshot: ListSnapshot<O> = .init() {
willSet {
self.willChange()
}
didSet {
self.didChange()
self.notifyObservers()
}
}
@@ -315,11 +299,6 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
self.fetchedResultsControllerDelegate.fetchedResultsController = nil
self.observers.removeAllObjects()
}
// MARK: FilePrivate
fileprivate let rawObjectWillChange: Any?
// MARK: Private
@@ -375,21 +354,6 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
applyFetchClauses: applyFetchClauses
)
if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
#if canImport(Combine)
self.rawObjectWillChange = ObservableObjectPublisher()
#else
self.rawObjectWillChange = nil
#endif
}
else {
self.rawObjectWillChange = nil
}
self.fetchedResultsControllerDelegate.handler = self
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
@@ -428,143 +392,3 @@ extension ListPublisher: FetchedDiffableDataSourceSnapshotHandler {
)
}
}
#if canImport(Combine)
import Combine
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ListPublisher: ObservableObject {}
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ListPublisher: Publisher {
// MARK: Publisher
public typealias Output = ListSnapshot<O>
public typealias Failure = Never
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
subscriber.receive(
subscription: ListSnapshotSubscription(
publisher: self,
subscriber: subscriber
)
)
}
// MARK: - ListSnapshotSubscriber
fileprivate final class ListSnapshotSubscriber: Subscriber {
// MARK: Subscriber
typealias Failure = Never
func receive(subscription: Subscription) {
subscription.request(.unlimited)
}
func receive(_ input: Output) -> Subscribers.Demand {
return .unlimited
}
func receive(completion: Subscribers.Completion<Failure>) {}
}
// MARK: - ListSnapshotSubscription
fileprivate final class ListSnapshotSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == Never {
// MARK: FilePrivate
init(publisher: ListPublisher<O>, subscriber: S) {
self.publisher = publisher
self.subscriber = subscriber
}
// MARK: Subscription
func request(_ demand: Subscribers.Demand) {
guard demand > 0 else {
return
}
self.publisher.addObserver(self) { [weak self] (publisher) in
guard let self = self, let subscriber = self.subscriber else {
return
}
_ = subscriber.receive(publisher.snapshot)
}
}
// MARK: Cancellable
func cancel() {
self.publisher.removeObserver(self)
self.subscriber = nil
}
// MARK: Private
private let publisher: ListPublisher<O>
private var subscriber: S?
}
}
#endif
// MARK: - ListPublisher
extension ListPublisher {
// MARK: ObservableObject
#if canImport(Combine)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
public var objectWillChange: ObservableObjectPublisher {
return self.rawObjectWillChange! as! ObservableObjectPublisher
}
#endif
fileprivate func willChange() {
guard #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) else {
return
}
#if canImport(Combine)
#if canImport(SwiftUI)
withAnimation {
self.objectWillChange.send()
}
#endif
self.objectWillChange.send()
#endif
}
fileprivate func didChange() {
// nothing
}
}

View File

@@ -70,7 +70,6 @@ public struct ListReader<Object: DynamicObject, Content: View, Value>: View {
self._list = .init(listPublisher)
self.content = content
self.keyPath = \.self
}
/**
@@ -98,8 +97,10 @@ public struct ListReader<Object: DynamicObject, Content: View, Value>: View {
) {
self._list = .init(listPublisher)
self.content = content
self.keyPath = keyPath
self.content = {
content($0[keyPath: keyPath])
}
}
@@ -107,17 +108,16 @@ public struct ListReader<Object: DynamicObject, Content: View, Value>: View {
public var body: some View {
self.content(self.list[keyPath: self.keyPath])
self.content(self.list)
}
// MARK: Private
@LiveList
@ListState
private var list: ListSnapshot<Object>
private let content: (Value) -> Content
private let keyPath: KeyPath<ListSnapshot<Object>, Value>
private let content: (ListSnapshot<Object>) -> Content
}
#endif

View File

@@ -1,5 +1,5 @@
//
// LiveList.swift
// ListState.swift
// CoreStore
//
// Copyright © 2021 John Rommel Estropia
@@ -29,21 +29,21 @@ import Combine
import SwiftUI
// MARK: - LiveList
// MARK: - ListState
/**
A property wrapper type that can read `ListPublisher` changes.
*/
@propertyWrapper
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
public struct LiveList<Object: DynamicObject>: DynamicProperty {
public struct ListState<Object: DynamicObject>: DynamicProperty {
// MARK: Public
/**
Creates an instance that observes `ListPublisher` changes and exposes a `ListSnapshot` value.
```
@LiveList
@ListState
var people: ListSnapshot<Person>
init(listPublisher: ListPublisher<Person>) {
@@ -64,7 +64,7 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
}
```
- parameter listPublisher: The `ListPublisher` that the `LiveList` will observe changes for
- parameter listPublisher: The `ListPublisher` that the `ListState` will observe changes for
*/
public init(
_ listPublisher: ListPublisher<Object>
@@ -76,10 +76,11 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
/**
Creates an instance that observes the specified `FetchChainableBuilderType` and exposes a `ListSnapshot` value.
```
@LiveList(
@ListState(
From<Person>()
.where(\.isMember == true)
.orderBy(.ascending(\.lastName))
.orderBy(.ascending(\.lastName)),
in: Globals.dataStack
)
var people: ListSnapshot<Person>
@@ -109,11 +110,12 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
/**
Creates an instance that observes the specified `SectionMonitorBuilderType` and exposes a `ListSnapshot` value.
```
@LiveList(
@ListState(
From<Person>()
.sectionBy(\.age)
.where(\.isMember == true)
.orderBy(.ascending(\.lastName))
.orderBy(.ascending(\.lastName)),
in: Globals.dataStack
)
var people: ListSnapshot<Person>
@@ -149,10 +151,11 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
/**
Creates an instance that observes the specified `From` and `FetchClause`s and exposes a `ListSnapshot` value.
```
@LiveList(
@ListState(
From<Person>(),
Where<Person>(\.isMember == true),
OrderBy<Person>(.ascending(\.lastName))
OrderBy<Person>(.ascending(\.lastName)),
in: Globals.dataStack
)
var people: ListSnapshot<Person>
@@ -184,12 +187,13 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
/**
Creates an instance that observes the specified `From` and `FetchClause`s and exposes a `ListSnapshot` value.
```
@LiveList(
@ListState(
From<Person>(),
[
Where<Person>(\.isMember == true),
OrderBy<Person>(.ascending(\.lastName))
]
],
in: Globals.dataStack
)
var people: ListSnapshot<Person>
@@ -221,11 +225,12 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
/**
Creates an instance that observes the specified `From`, `SectionBy`, and `FetchClause`s and exposes a sectioned `ListSnapshot` value.
```
@LiveList(
@ListState(
From<Person>(),
SectionBy(\.age),
Where<Person>(\.isMember == true),
OrderBy<Person>(.ascending(\.lastName))
OrderBy<Person>(.ascending(\.lastName)),
in: Globals.dataStack
)
var people: ListSnapshot<Person>
@@ -265,13 +270,14 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
/**
Creates an instance that observes the specified `From`, `SectionBy`, and `FetchClause`s and exposes a sectioned `ListSnapshot` value.
```
@LiveList(
@ListState(
From<Person>(),
SectionBy(\.age),
[
Where<Person>(\.isMember == true),
OrderBy<Person>(.ascending(\.lastName))
]
],
in: Globals.dataStack
)
var people: ListSnapshot<Person>

View File

@@ -29,7 +29,6 @@ import CoreData
// MARK: - DataStack
@available(macOS 10.12, *)
extension DataStack {
/**
@@ -114,7 +113,6 @@ extension DataStack {
// MARK: - UnsafeDataTransaction
@available(macOS 10.12, *)
extension UnsafeDataTransaction {
/**
@@ -204,7 +202,6 @@ extension Internals {
// MARK: FilePrivate
@available(macOS 10.12, *)
fileprivate static func createFRC<O: NSManagedObject>(fromContext context: NSManagedObjectContext, from: From<O>, sectionBy: SectionBy<O>? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController<O> {
let controller = Internals.CoreStoreFetchedResultsController(

View File

@@ -230,14 +230,7 @@ extension NSManagedObjectContext {
@nonobjc
internal func refreshAndMergeAllObjects() {
if #available(iOS 8.3, macOS 10.11, *) {
self.refreshAllObjects()
}
else {
self.registeredObjects.forEach { self.refresh($0, mergeChanges: true) }
}
self.refreshAllObjects()
}

View File

@@ -39,7 +39,6 @@ import CoreData
Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
*/
@available(macOS 10.12, *)
public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentation {
/**
@@ -376,7 +375,6 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
// MARK: - ObjectMonitor: FetchedResultsControllerHandler
@available(macOS 10.12, *)
extension ObjectMonitor: FetchedResultsControllerHandler {
// MARK: FetchedResultsControllerHandler
@@ -426,7 +424,6 @@ extension ObjectMonitor: FetchedResultsControllerHandler {
// MARK: - Notification.Name
@available(macOS 10.12, *)
extension Notification.Name {
fileprivate static let objectMonitorWillChangeObject = Notification.Name(rawValue: "objectMonitorWillChangeObject")

View File

@@ -36,7 +36,6 @@ import CoreData
monitor.addObserver(self)
```
*/
@available(macOS 10.12, *)
public protocol ObjectObserver: AnyObject {
/**
@@ -76,7 +75,6 @@ public protocol ObjectObserver: AnyObject {
// MARK: - ObjectObserver (Default Implementations)
@available(macOS 10.12, *)
extension ObjectObserver {
public func objectMonitor(_ monitor: ObjectMonitor<ObjectEntityType>, willUpdateObject object: ObjectEntityType) { }

View File

@@ -37,6 +37,8 @@ extension ObjectPublisher {
/**
A `Publisher` that emits an `ObjectSnapshot?` whenever changes occur in the `ObjectPublisher`. The event emits `nil` if the object has been deletd.
- SeeAlso: ObjectPublisher.reactive.snapshot(emitInitialValue:)
*/
public struct SnapshotPublisher: Publisher {

View File

@@ -25,16 +25,6 @@
import CoreData
#if canImport(Combine)
import Combine
#endif
#if canImport(SwiftUI)
import SwiftUI
#endif
// MARK: - ObjectPublisher
@@ -226,26 +216,10 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
// MARK: FilePrivate
fileprivate let rawObjectWillChange: Any?
fileprivate init(objectID: O.ObjectID, context: NSManagedObjectContext, initializer: @escaping (NSManagedObjectID, NSManagedObjectContext) -> ObjectSnapshot<O>?) {
self.id = objectID
self.context = context
if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
#if canImport(Combine)
self.rawObjectWillChange = ObservableObjectPublisher()
#else
self.rawObjectWillChange = nil
#endif
}
else {
self.rawObjectWillChange = nil
}
self.$lazySnapshot.initialize { [weak self] in
guard let self = self else {
@@ -262,16 +236,12 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
self.object = nil
self.willChange()
self.$lazySnapshot.reset({ nil })
self.didChange()
self.notifyObservers()
}
else if updatedIDs.contains(objectID) {
self.willChange()
self.$lazySnapshot.reset({ initializer(objectID, context) })
self.didChange()
self.notifyObservers()
}
}
@@ -305,154 +275,6 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
}
#if canImport(Combine)
import Combine
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ObjectPublisher: ObservableObject {}
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ObjectPublisher: Publisher {
// MARK: Publisher
public typealias Output = ObjectSnapshot<O>
public typealias Failure = Never
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
subscriber.receive(
subscription: ObjectSnapshotSubscription(
publisher: self,
subscriber: subscriber
)
)
}
// MARK: - ObjectSnapshotSubscriber
fileprivate final class ObjectSnapshotSubscriber: Subscriber {
// MARK: Subscriber
typealias Failure = Never
func receive(subscription: Subscription) {
subscription.request(.unlimited)
}
func receive(_ input: Output) -> Subscribers.Demand {
return .unlimited
}
func receive(completion: Subscribers.Completion<Failure>) {}
}
// MARK: - ObjectSnapshotSubscription
fileprivate final class ObjectSnapshotSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == Never {
// MARK: FilePrivate
init(publisher: ObjectPublisher<O>, subscriber: S) {
self.publisher = publisher
self.subscriber = subscriber
}
// MARK: Subscription
func request(_ demand: Subscribers.Demand) {
guard demand > 0 else {
return
}
self.publisher.addObserver(self) { [weak self] (publisher) in
guard let self = self, let subscriber = self.subscriber else {
return
}
if let snapshot = publisher.snapshot {
_ = subscriber.receive(snapshot)
}
else {
subscriber.receive(completion: .finished)
}
}
}
// MARK: Cancellable
func cancel() {
self.publisher.removeObserver(self)
self.subscriber = nil
}
// MARK: Private
private let publisher: ObjectPublisher<O>
private var subscriber: S?
}
}
#endif
// MARK: - ObjectPublisher
extension ObjectPublisher {
// MARK: ObservableObject
#if canImport(Combine)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
public var objectWillChange: ObservableObjectPublisher {
return self.rawObjectWillChange! as! ObservableObjectPublisher
}
#endif
fileprivate func willChange() {
guard #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) else {
return
}
#if canImport(Combine)
#if canImport(SwiftUI)
withAnimation {
self.objectWillChange.send()
}
#endif
self.objectWillChange.send()
#endif
}
fileprivate func didChange() {
// nothing
}
}
// MARK: - ObjectPublisher where O: NSManagedObject
extension ObjectPublisher where O: NSManagedObject {

View File

@@ -35,7 +35,7 @@ import SwiftUI
A container view that reads changes to an `ObjectPublisher`
*/
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
public struct ObjectReader<Object: DynamicObject, Content: View, Value>: View {
public struct ObjectReader<Object: DynamicObject, Content: View, Placeholder: View, Value>: View {
// MARK: Internal
@@ -48,11 +48,29 @@ public struct ObjectReader<Object: DynamicObject, Content: View, Value>: View {
public init(
_ objectPublisher: ObjectPublisher<Object>?,
@ViewBuilder content: @escaping (ObjectSnapshot<Object>) -> Content
) where Value == ObjectSnapshot<Object>, Placeholder == EmptyView {
self._object = .init(objectPublisher)
self.content = content
self.placeholder = EmptyView.init
}
/**
Creates an instance that creates views for `ObjectPublisher` changes.
- parameter objectPublisher: The `ObjectPublisher` that the `ObjectReader` instance uses to create views dynamically
- parameter content: The view builder that receives an `Optional<ObjectSnapshot<O>>` instance and creates views dynamically.
- parameter placeholder: The view builder that creates a view for `nil` objects.
*/
public init(
_ objectPublisher: ObjectPublisher<Object>?,
@ViewBuilder content: @escaping (ObjectSnapshot<Object>) -> Content,
@ViewBuilder placeholder: @escaping () -> Placeholder
) where Value == ObjectSnapshot<Object> {
self._object = .init(objectPublisher)
self.content = content
self.keyPath = \.self
self.placeholder = placeholder
}
/**
@@ -66,11 +84,37 @@ public struct ObjectReader<Object: DynamicObject, Content: View, Value>: View {
_ objectPublisher: ObjectPublisher<Object>?,
keyPath: KeyPath<ObjectSnapshot<Object>, Value>,
@ViewBuilder content: @escaping (Value) -> Content
) {
) where Placeholder == EmptyView {
self._object = .init(objectPublisher)
self.content = content
self.keyPath = keyPath
self.content = {
content($0[keyPath: keyPath])
}
self.placeholder = EmptyView.init
}
/**
Creates an instance that creates views for `ObjectPublisher` changes.
- parameter objectPublisher: The `ObjectPublisher` that the `ObjectReader` instance uses to create views dynamically
- parameter keyPath: A `KeyPath` for a property in the `ObjectSnapshot` whose value will be sent to the views
- parameter content: The view builder that receives the value from the property `KeyPath` and creates views dynamically.
- parameter placeholder: The view builder that creates a view for `nil` objects.
*/
public init(
_ objectPublisher: ObjectPublisher<Object>?,
keyPath: KeyPath<ObjectSnapshot<Object>, Value>,
@ViewBuilder content: @escaping (Value) -> Content,
@ViewBuilder placeholder: @escaping () -> Placeholder
) where Placeholder == EmptyView {
self._object = .init(objectPublisher)
self.content = {
content($0[keyPath: keyPath])
}
self.placeholder = placeholder
}
@@ -80,18 +124,22 @@ public struct ObjectReader<Object: DynamicObject, Content: View, Value>: View {
if let object = self.object {
self.content(object[keyPath: self.keyPath])
self.content(object)
}
else {
self.placeholder()
}
}
// MARK: Private
@LiveObject
@ObjectState
private var object: ObjectSnapshot<Object>?
private let content: (Value) -> Content
private let keyPath: KeyPath<ObjectSnapshot<Object>, Value>
private let content: (ObjectSnapshot<Object>) -> Content
private let placeholder: () -> Placeholder
}
#endif

View File

@@ -127,6 +127,11 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable {
self.values = values
self.generation = .init()
}
internal var cs_objectID: O.ObjectID {
return self.objectID()
}
// MARK: FilePrivate

View File

@@ -1,5 +1,5 @@
//
// LiveObject.swift
// ObjectState.swift
// CoreStore
//
// Copyright © 2021 John Rommel Estropia
@@ -29,21 +29,21 @@ import Combine
import SwiftUI
// MARK: - LiveObject
// MARK: - ObjectState
/**
A property wrapper type that can read `ObjectPublisher` changes.
*/
@propertyWrapper
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
public struct LiveObject<O: DynamicObject>: DynamicProperty {
public struct ObjectState<O: DynamicObject>: DynamicProperty {
// MARK: Public
/**
Creates an instance that observes `ObjectPublisher` changes and exposes an `Optional<ObjectSnapshot<O>>` value.
```
@LiveObject
@ObjectState
var person: ObjectSnapshot<Person>?
init(objectPublisher: ObjectPublisher<Person>) {
@@ -61,7 +61,7 @@ public struct LiveObject<O: DynamicObject>: DynamicProperty {
}
```
- parameter objectPublisher: The `ObjectPublisher` that the `LiveObject` will observe changes for
- parameter objectPublisher: The `ObjectPublisher` that the `ObjectState` will observe changes for
*/
public init(_ objectPublisher: ObjectPublisher<O>?) {
@@ -76,6 +76,11 @@ public struct LiveObject<O: DynamicObject>: DynamicProperty {
return self.observer.item
}
public var projectedValue: ObjectPublisher<O>? {
return self.observer.objectPublisher
}
// MARK: DynamicProperty

View File

@@ -205,15 +205,10 @@ public final class SQLiteStore: LocalStorage {
[NSSQLitePragmasOption: ["journal_mode": "WAL"]]
```
*/
public let storeOptions: [AnyHashable: Any]? = autoreleasepool {
var storeOptions: [AnyHashable: Any] = [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
if #available(iOS 11.0, macOS 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *) {
storeOptions[NSBinaryStoreInsecureDecodingCompatibilityOption] = true
}
return storeOptions
}
public let storeOptions: [AnyHashable: Any]? = [
NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSBinaryStoreInsecureDecodingCompatibilityOption: true
]
/**
Do not call directly. Used by the `DataStack` internally.

View File

@@ -39,7 +39,6 @@ import CoreData
)
```
*/
@available(macOS 10.12, *)
public struct SectionBy<O: DynamicObject> {
/**
@@ -99,7 +98,6 @@ public struct SectionBy<O: DynamicObject> {
// MARK: - SectionBy where O: NSManagedObject
@available(macOS 10.12, *)
extension SectionBy where O: NSManagedObject {
/**
@@ -152,7 +150,6 @@ extension SectionBy where O: NSManagedObject {
// MARK: - SectionBy where O: CoreStoreObject
@available(macOS 10.12, *)
extension SectionBy where O: CoreStoreObject {
/**

View File

@@ -40,7 +40,6 @@ import CoreData
)
```
*/
@available(macOS 10.12, *)
public struct SectionMonitorChainBuilder<O: DynamicObject>: SectionMonitorBuilderType {
// MARK: SectionMonitorBuilderType
@@ -62,7 +61,6 @@ public struct SectionMonitorChainBuilder<O: DynamicObject>: SectionMonitorBuilde
/**
Utility protocol for `SectionMonitorChainBuilder`. Used in methods that support chained fetch builders.
*/
@available(macOS 10.12, *)
public protocol SectionMonitorBuilderType {
/**

View File

@@ -29,7 +29,6 @@ import CoreData
// MARK: - UnsafeDataTransaction
@available(macOS 10.12, *)
extension UnsafeDataTransaction {
/**