SwiftUI and Combine utilities appledocs. Bump up to 8.0 and iOS 11.0/macOS 10.13

This commit is contained in:
John Estropia
2021-03-01 09:14:41 +09:00
parent d13b0cfabb
commit 1f97225efa
12 changed files with 669 additions and 150 deletions

View File

@@ -31,10 +31,47 @@ import Combine
// MARK: - DataStack
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension DataStack: PublisherCompatible {}
extension DataStack {
// MARK: Public
/**
Combine utilities for the `DataStack` are exposed through this namespace
*/
public var reactive: DataStack.ReactiveNamespace {
return .init(self)
}
// MARK: - ReactiveNamespace
/**
Combine utilities for the `DataStack` are exposed through this namespace. Extend this type if you need to add other Combine Publisher utilities for `DataStack`.
*/
public struct ReactiveNamespace {
// MARK: Public
/**
The `DataStack` instance
*/
public let base: DataStack
// MARK: Internal
internal init(_ base: DataStack) {
self.base = base
}
}
}
// MARK: - DataStack.ReactiveNamespace
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension PublisherProvider where Base == DataStack {
extension DataStack.ReactiveNamespace {
// MARK: Public

View File

@@ -0,0 +1,109 @@
//
// ListPublisher+Reactive.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
// MARK: - ListPublisher
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ListPublisher {
// MARK: Public
/**
Combine utilities for the `ListPublisher` are exposed through this namespace
*/
public var reactive: ListPublisher.ReactiveNamespace {
return .init(self)
}
// MARK: - ReactiveNamespace
/**
Combine utilities for the `ListPublisher` are exposed through this namespace. Extend this type if you need to add other Combine Publisher utilities for `ListPublisher`.
*/
public struct ReactiveNamespace {
// MARK: Public
/**
The `ListPublisher` instance
*/
public let base: ListPublisher
// MARK: Internal
internal init(_ base: ListPublisher) {
self.base = base
}
}
}
// MARK: - ListPublisher.ReactiveNamespace
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ListPublisher.ReactiveNamespace {
// MARK: Public
/**
Returns a `Publisher` that emits a `ListSnapshot` whenever changes occur in the `ListPublisher`
```
listPublisher.reactive
.snapshot(emitInitialValue: true)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (listSnapshot) in
dataSource.apply(
listSnapshot,
animatingDifferences: true
)
}
)
.store(in: &cancellables)
```
- parameter emitInitialValue: If `true`, the current value is immediately emitted to the first subscriber. If `false`, the event fires only starting the next `ListSnapshot` update.
- returns: A `Publisher` that emits a `ListSnapshot` whenever changes occur in the `ListPublisher`.
*/
public func snapshot(emitInitialValue: Bool = true) -> ListPublisher.SnapshotPublisher {
return .init(
listPublisher: self.base,
emitInitialValue: emitInitialValue
)
}
}
#endif

View File

@@ -0,0 +1,150 @@
//
// ListPublisher.SnapshotPublisher.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: - ListPublisher
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ListPublisher {
// MARK: - SnapshotPublisher
/**
A `Publisher` that emits a `ListSnapshot` whenever changes occur in the `ListPublisher`.
*/
public struct SnapshotPublisher: Publisher {
// MARK: Internal
internal let listPublisher: ListPublisher<O>
internal let emitInitialValue: Bool
// 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.listPublisher,
emitInitialValue: self.emitInitialValue,
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>,
emitInitialValue: Bool,
subscriber: S
) {
self.publisher = publisher
self.emitInitialValue = emitInitialValue
self.subscriber = subscriber
}
// MARK: Subscription
func request(_ demand: Subscribers.Demand) {
guard demand > 0 else {
return
}
self.publisher.addObserver(
self,
notifyInitial: self.emitInitialValue,
{ [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 let emitInitialValue: Bool
private var subscriber: S?
}
}
}
#endif

View File

@@ -0,0 +1,161 @@
//
// ObjectPublisher+Reactive.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
// MARK: - ObjectPublisher
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ObjectPublisher {
// MARK: Public
/**
Combine utilities for the `ObjectPublisher` are exposed through this namespace
*/
public var reactive: ObjectPublisher.ReactiveNamespace {
return .init(self)
}
// MARK: - ReactiveNamespace
/**
Combine utilities for the `ObjectPublisher` are exposed through this namespace. Extend this type if you need to add other Combine Publisher utilities for `ObjectPublisher`.
*/
@dynamicMemberLookup
public struct ReactiveNamespace {
// MARK: Public
/**
The `ObjectPublisher` instance
*/
public let base: ObjectPublisher
// MARK: Internal
internal init(_ base: ObjectPublisher) {
self.base = base
}
}
}
// MARK: - ObjectPublisher.ReactiveNamespace
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ObjectPublisher.ReactiveNamespace {
// MARK: Public
/**
Returns a `Publisher` that emits an `ObjectSnapshot?` whenever changes occur in the `ObjectPublisher`. The event emits `nil` if the object has been deletd.
```
objectPublisher.reactive
.snapshot(emitInitialValue: true)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (objectSnapshot) in
tableViewCell.setObject(objectSnapshot)
}
)
.store(in: &tableViewCell.cancellables)
```
- parameter emitInitialValue: If `true`, the current value is immediately emitted to the first subscriber. If `false`, the event fires only starting the next `ObjectSnapshot` update.
- returns: A `Publisher` that emits an `ObjectSnapshot?` whenever changes occur in the `ObjectPublisher`. The event emits `nil` if the object has been deletd.
*/
public func snapshot(emitInitialValue: Bool = true) -> ObjectPublisher.SnapshotPublisher {
return .init(
objectPublisher: self.base,
emitInitialValue: emitInitialValue
)
}
}
// MARK: - ObjectPublisher.ReactiveNamespace where O: NSManagedObject
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ObjectPublisher.ReactiveNamespace where O: NSManagedObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<V: AllowedObjectiveCKeyPathValue>(dynamicMember member: KeyPath<O, V>) -> some Publisher {
return self
.snapshot(emitInitialValue: true)
.map({ $0?[dynamicMember: member] })
}
}
// MARK: - ObjectPublisher.ReactiveNamespace where O: CoreStoreObject
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ObjectPublisher.ReactiveNamespace where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Stored<V>>) -> some Publisher {
return self
.snapshot(emitInitialValue: true)
.map({ $0?[dynamicMember: member] })
}
/**
Returns the value for the property identified by a given key.
*/
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Virtual<V>>) -> some Publisher {
return self
.snapshot(emitInitialValue: true)
.map({ $0?[dynamicMember: member] })
}
/**
Returns the value for the property identified by a given key.
*/
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Coded<V>>) -> some Publisher {
return self
.snapshot(emitInitialValue: true)
.map({ $0?[dynamicMember: member] })
}
}
#endif

View File

@@ -0,0 +1,150 @@
//
// ObjectPublisher.SnapshotPublisher.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: - ObjectPublisher
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ObjectPublisher {
// MARK: - SnapshotPublisher
/**
A `Publisher` that emits an `ObjectSnapshot?` whenever changes occur in the `ObjectPublisher`. The event emits `nil` if the object has been deletd.
*/
public struct SnapshotPublisher: Publisher {
// MARK: Internal
internal let objectPublisher: ObjectPublisher<O>
internal let emitInitialValue: Bool
// 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.objectPublisher,
emitInitialValue: self.emitInitialValue,
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>,
emitInitialValue: Bool,
subscriber: S
) {
self.publisher = publisher
self.emitInitialValue = emitInitialValue
self.subscriber = subscriber
}
// MARK: Subscription
func request(_ demand: Subscribers.Demand) {
guard demand > 0 else {
return
}
self.publisher.addObserver(
self,
notifyInitial: self.emitInitialValue,
{ [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: ObjectPublisher<O>
private let emitInitialValue: Bool
private var subscriber: S?
}
}
}
#endif

View File

@@ -174,21 +174,6 @@ extension ObjectSnapshot where O: NSManagedObject {
self.values[key] = newValue
}
}
// MARK: Deprecated
@available(*, deprecated, message: "Accessing the property directly now works")
public func value<V: AllowedObjectiveCKeyPathValue>(forKeyPath keyPath: KeyPath<O, V>) -> V! {
return self[dynamicMember: keyPath]
}
@available(*, deprecated, message: "Mutating the property directly now works")
public mutating func setValue<V: AllowedObjectiveCKeyPathValue>(_ value: V!, forKeyPath keyPath: KeyPath<O, V>) {
self[dynamicMember: keyPath] = value
}
}

View File

@@ -1,52 +0,0 @@
//
// PublisherCompatible.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
// MARK: - PublisherCompatible
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
public protocol PublisherCompatible {
associatedtype ReactiveBase
var reactive: PublisherProvider<ReactiveBase> { get }
}
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension PublisherCompatible {
// MARK: Public
public var reactive: PublisherProvider<Self> {
return .init(self)
}
}
#endif

View File

@@ -1,46 +0,0 @@
//
// PublisherProvider.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
// MARK: - PublisherProvider
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
public struct PublisherProvider<Base> {
// MARK: Public
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
#endif