diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonType.swift b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonType.swift index c3c1e6a..f7546bf 100644 --- a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonType.swift +++ b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonType.swift @@ -10,7 +10,7 @@ extension Modern.PokedexDemo { // MARK: - Modern.PokedexDemo.Move - enum PokemonType: String, CaseIterable, FieldStorableType { + enum PokemonType: String, CaseIterable, ImportableAttributeType, FieldStorableType { // MARK: Internal diff --git a/Sources/From+Querying.swift b/Sources/From+Querying.swift index 9bc8e99..2b4c597 100644 --- a/Sources/From+Querying.swift +++ b/Sources/From+Querying.swift @@ -808,6 +808,39 @@ extension QueryChainBuilder where O: CoreStoreObject { return self.queryChain(appending: clause(O.meta)) } + /** + Adds a `GroupBy` clause to the `QueryChainBuilder` + + - parameter keyPath: a key path to group the query results with + - returns: a new `QueryChainBuilder` containing the `GroupBy` clause + */ + public func groupBy(_ keyPath: KeyPath.Stored>) -> QueryChainBuilder { + + return self.groupBy(GroupBy(keyPath)) + } + + /** + Adds a `GroupBy` clause to the `QueryChainBuilder` + + - parameter keyPath: a key path to group the query results with + - returns: a new `QueryChainBuilder` containing the `GroupBy` clause + */ + public func groupBy(_ keyPath: KeyPath.Virtual>) -> QueryChainBuilder { + + return self.groupBy(GroupBy(keyPath)) + } + + /** + Adds a `GroupBy` clause to the `QueryChainBuilder` + + - parameter keyPath: a key path to group the query results with + - returns: a new `QueryChainBuilder` containing the `GroupBy` clause + */ + public func groupBy(_ keyPath: KeyPath.Coded>) -> QueryChainBuilder { + + return self.groupBy(GroupBy(keyPath)) + } + /** Adds a `GroupBy` clause to the `QueryChainBuilder` diff --git a/Sources/GroupBy.swift b/Sources/GroupBy.swift index 4b80a2b..c17067d 100644 --- a/Sources/GroupBy.swift +++ b/Sources/GroupBy.swift @@ -124,6 +124,36 @@ extension GroupBy where O: NSManagedObject { extension GroupBy where O: CoreStoreObject { + /** + Initializes a `GroupBy` clause with a key path + + - parameter keyPath: a key path to group results with + */ + public init(_ keyPath: KeyPath.Stored>) { + + self.init([O.meta[keyPath: keyPath].keyPath]) + } + + /** + Initializes a `GroupBy` clause with a key path + + - parameter keyPath: a key path to group results with + */ + public init(_ keyPath: KeyPath.Virtual>) { + + self.init([O.meta[keyPath: keyPath].keyPath]) + } + + /** + Initializes a `GroupBy` clause with a key path + + - parameter keyPath: a key path to group results with + */ + public init(_ keyPath: KeyPath.Coded>) { + + self.init([O.meta[keyPath: keyPath].keyPath]) + } + /** Initializes a `GroupBy` clause with a key path diff --git a/Sources/ListPublisher.swift b/Sources/ListPublisher.swift index 7d078b7..1b80ca6 100644 --- a/Sources/ListPublisher.swift +++ b/Sources/ListPublisher.swift @@ -419,6 +419,94 @@ 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 + public typealias Failure = Never + + public func receive(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) {} + } + + + // MARK: - ListSnapshotSubscription + + fileprivate final class ListSnapshotSubscription: Subscription where S.Input == Output, S.Failure == Never { + + // MARK: FilePrivate + + init(publisher: ListPublisher, 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 + private var subscriber: S? + } +} + #endif // MARK: - ListPublisher diff --git a/Sources/ObjectProxy.swift b/Sources/ObjectProxy.swift index cd2f61e..b8f3923 100644 --- a/Sources/ObjectProxy.swift +++ b/Sources/ObjectProxy.swift @@ -79,7 +79,7 @@ public struct ObjectProxy { /** Returns the value for the specified property from the object’s private internal storage. - Accessing this property does not invoke the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`). This method is used primarily to implement custom accessor methods that need direct access to the object's private storage. + This method is used primarily to implement custom accessor methods that need direct access to the object's private storage. */ public var primitiveValue: V? { @@ -95,8 +95,6 @@ public struct ObjectProxy { /** Returns the value for the property identified by a given key. - - Accessing this property triggers the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`). */ public var value: V { @@ -147,6 +145,11 @@ public struct ObjectProxy { } self.setPrimitiveValue = { + rawObject.willChangeValue(forKey: keyPathString) + defer { + + rawObject.didChangeValue(forKey: keyPathString) + } rawObject.setPrimitiveValue( $0.cs_toFieldStoredNativeType(), forKey: keyPathString @@ -179,7 +182,7 @@ public struct ObjectProxy { } } self.setPrimitiveValue = { - + rawObject.setPrimitiveValue( $0, forKey: keyPathString @@ -216,7 +219,12 @@ public struct ObjectProxy { } } self.setPrimitiveValue = { + + rawObject.willChangeValue(forKey: keyPathString) + defer { + rawObject.didChangeValue(forKey: keyPathString) + } rawObject.setPrimitiveValue( $0, forKey: keyPathString diff --git a/Sources/ObjectPublisher.swift b/Sources/ObjectPublisher.swift index 10f952b..e0c7458 100644 --- a/Sources/ObjectPublisher.swift +++ b/Sources/ObjectPublisher.swift @@ -288,6 +288,101 @@ 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 + public typealias Failure = Never + + public func receive(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) {} + } + + + // MARK: - ObjectSnapshotSubscription + + fileprivate final class ObjectSnapshotSubscription: Subscription where S.Input == Output, S.Failure == Never { + + // MARK: FilePrivate + + init(publisher: ObjectPublisher, 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 + private var subscriber: S? + } +} + #endif // MARK: - ObjectPublisher