Skip to content

Commit

Permalink
Migrate from Combine to Observable framework
Browse files Browse the repository at this point in the history
Fixes: #12
  • Loading branch information
lhoward committed Dec 1, 2024
1 parent 905c23b commit 34af3ce
Show file tree
Hide file tree
Showing 23 changed files with 140 additions and 193 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ let CommonTargets: [Target] = [
let package = Package(
name: "SwiftOCA",
platforms: [
.macOS(.v13),
.macOS(.v14),
.iOS(.v16),
],
products: CommonProducts + PlatformProducts,
Expand Down
48 changes: 22 additions & 26 deletions Sources/SwiftOCA/OCC/ControlClasses/Root.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,28 @@

import AsyncExtensions
import Foundation
#if canImport(Combine)
import Combine
#elseif canImport(OpenCombine)
import OpenCombine
#else
protocol ObservableObject {} // placeholder
#endif
#if canImport(SwiftUI)
import SwiftUI
#endif

open class OcaRoot: CustomStringConvertible, ObservableObject, @unchecked Sendable,
OcaKeyPathMarkerProtocol
import Observation

open class OcaRoot: CustomStringConvertible, @unchecked Sendable,
OcaKeyPathMarkerProtocol, Observable
{
typealias Root = OcaRoot

public internal(set) weak var connectionDelegate: Ocp1Connection?

fileprivate var subscriptionCancellable: Ocp1Connection.SubscriptionCancellable?
fileprivate let _$observationRegistrar = Observation.ObservationRegistrar()

// 1.1
open class var classID: OcaClassID { OcaClassID("1") }

private var _classID: StaticProperty<OcaClassID> {
StaticProperty<OcaClassID>(propertyIDs: [OcaPropertyID("1.1")], value: Self.classID)
}

// 1.2
open class var classVersion: OcaClassVersionNumber { 3 }

private var _classVersion: StaticProperty<OcaClassVersionNumber> {
StaticProperty<OcaClassVersionNumber>(
propertyIDs: [OcaPropertyID("1.2")],
Expand Down Expand Up @@ -185,14 +180,27 @@ protocol OcaKeyPathMarkerProtocol: AnyObject {}
extension PartialKeyPath: @unchecked
Sendable {} // fix warning

private extension OcaKeyPathMarkerProtocol where Self: OcaRoot {
extension OcaKeyPathMarkerProtocol where Self: OcaRoot {
var allKeyPaths: [String: PartialKeyPath<Self>] {
_allKeyPaths(value: self).reduce(into: [:]) {
if $1.key.hasPrefix("_") {
$0[String($1.key.dropFirst())] = $1.value
}
}
}

nonisolated func access(
keyPath: KeyPath<Self, some Any>
) {
_$observationRegistrar.access(self, keyPath: keyPath)
}

nonisolated func withMutation<T>(
keyPath: KeyPath<Self, some Any>,
_ mutation: () throws -> T
) rethrows -> T {
try _$observationRegistrar.withMutation(of: self, keyPath: keyPath, mutation)
}
}

public extension OcaRoot {
Expand Down Expand Up @@ -301,18 +309,6 @@ public extension OcaRoot {
AsyncCurrentValueSubject(currentValue)
}

#if canImport(SwiftUI)
var binding: Binding<PropertyValue> {
Binding(
get: {
currentValue
},
set: { _ in
}
)
}
#endif

@_spi(SwiftOCAPrivate) @discardableResult
public func _getValue(
_ object: OcaRoot,
Expand Down
32 changes: 5 additions & 27 deletions Sources/SwiftOCA/OCC/PropertyTypes/BoundedProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,18 +137,16 @@ public struct OcaBoundedProperty<
storage storageKeyPath: ReferenceWritableKeyPath<T, Self>
) -> PropertyValue {
get {
#if canImport(SwiftUI)
object[keyPath: storageKeyPath]._storage._referenceObject(_enclosingInstance: object)
#endif
object[keyPath: storageKeyPath]._storage._registerObservable(
_enclosingInstance: object,
wrapped: wrappedKeyPath
)
return object[keyPath: storageKeyPath]._storage
._get(
_enclosingInstance: object
)
}
set {
#if canImport(SwiftUI)
object[keyPath: storageKeyPath]._storage._referenceObject(_enclosingInstance: object)
#endif
object[keyPath: storageKeyPath]._storage._set(
_enclosingInstance: object,
newValue
Expand Down Expand Up @@ -181,7 +179,7 @@ public struct OcaBoundedProperty<
throw Ocp1Error.unhandledEvent
}

_storage._send(_enclosingInstance: object, .success(value))
_storage._send(object, .success(value))
}

public var projectedValue: Self {
Expand Down Expand Up @@ -215,23 +213,3 @@ public struct OcaBoundedProperty<
try await _storage.setValueIfMutable(object, value)
}
}

#if canImport(SwiftUI)
public extension OcaBoundedProperty {
var binding: Binding<PropertyValue> {
Binding(
get: {
if let object = _storage.object {
_storage._get(_enclosingInstance: object)
} else {
.initial
}
},
set: {
guard let object = _storage.object else { return }
_storage._set(_enclosingInstance: object, $0)
}
)
}
}
#endif
85 changes: 30 additions & 55 deletions Sources/SwiftOCA/OCC/PropertyTypes/Property.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Foundation
#if canImport(SwiftUI)
import SwiftUI
#endif
import Observation

public struct OcaPropertyResolutionFlags: OptionSet, Sendable {
public typealias RawValue = UInt32
Expand Down Expand Up @@ -61,10 +62,6 @@ public protocol OcaPropertyRepresentable: CustomStringConvertible {

func getJsonValue(_ object: OcaRoot, flags: OcaPropertyResolutionFlags) async throws
-> [String: Any]

#if canImport(SwiftUI)
var binding: Binding<PropertyValue> { get }
#endif
}

public extension OcaPropertyRepresentable {
Expand Down Expand Up @@ -164,6 +161,8 @@ public struct OcaProperty<Value: Codable & Sendable>: Codable, Sendable,
}
}

var _send: (@Sendable (_: OcaRoot?, _: PropertyValue) -> ())!

@_spi(SwiftOCAPrivate)
public let subject: AsyncCurrentValueSubject<PropertyValue>

Expand Down Expand Up @@ -193,27 +192,10 @@ public struct OcaProperty<Value: Codable & Sendable>: Codable, Sendable,
nonmutating set { fatalError() }
}

#if canImport(SwiftUI)
private(set) weak var object: OcaRoot?

mutating func _referenceObject(_enclosingInstance object: OcaRoot) {
self.object = object
}
#endif

public var currentValue: PropertyValue {
subject.value
}

func _send(_enclosingInstance object: OcaRoot, _ state: PropertyValue) {
#if canImport(Combine) || canImport(OpenCombine)
DispatchQueue.main.async {
object.objectWillChange.send()
}
#endif
subject.send(state)
}

/// It's not possible to wrap `subscript(_enclosingInstance:wrapped:storage:)` because we can't
/// cast struct key paths to `ReferenceWritableKeyPath`. For property wrapper wrappers, use
/// these internal get/set functions.
Expand Down Expand Up @@ -243,9 +225,9 @@ public struct OcaProperty<Value: Codable & Sendable>: Codable, Sendable,
try await setValueIfMutable(object, value)
} catch is CancellationError {
// if task cancelled due to a view being dismissed, reset state to initial
_send(_enclosingInstance: object, .initial)
_send(object, .initial)
} catch {
_send(_enclosingInstance: object, .failure(error))
_send(object, .failure(error))
await object.connectionDelegate?.logger.trace(
"set property handler for \(object) property \(propertyID) received error from device: \(error)"
)
Expand All @@ -258,22 +240,34 @@ public struct OcaProperty<Value: Codable & Sendable>: Codable, Sendable,
}
}

mutating func _registerObservable<T: OcaRoot>(
_enclosingInstance object: T,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<T, PropertyValue>
) {
object.access(keyPath: wrappedKeyPath)

_send = { @Sendable [subject] object, newValue in
guard let object else { return }
object.withMutation(keyPath: wrappedKeyPath) {
subject.send(newValue)
}
}
}

public static subscript<T: OcaRoot>(
_enclosingInstance object: T,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<T, PropertyValue>,
storage storageKeyPath: ReferenceWritableKeyPath<T, Self>
) -> PropertyValue {
get {
#if canImport(SwiftUI)
object[keyPath: storageKeyPath]._referenceObject(_enclosingInstance: object)
#endif
object[keyPath: storageKeyPath]._registerObservable(
_enclosingInstance: object,
wrapped: wrappedKeyPath
)
return object[keyPath: storageKeyPath]
._get(_enclosingInstance: object)
}
set {
#if canImport(SwiftUI)
object[keyPath: storageKeyPath]._referenceObject(_enclosingInstance: object)
#endif
object[keyPath: storageKeyPath]
._set(_enclosingInstance: object, newValue)
}
Expand All @@ -294,6 +288,7 @@ public struct OcaProperty<Value: Codable & Sendable>: Codable, Sendable,
self.setMethodID = setMethodID
subject = AsyncCurrentValueSubject(PropertyValue.initial)
self.setValueTransformer = setValueTransformer
_send = { @Sendable [subject] in subject.send($1) }
}

public init(
Expand Down Expand Up @@ -342,18 +337,18 @@ public struct OcaProperty<Value: Codable & Sendable>: Codable, Sendable,

let returnValue: Value = try await object.sendCommandRrq(methodID: getMethodID)
if flags.contains(.cacheValue) {
_send(_enclosingInstance: object, .success(returnValue))
_send(object, .success(returnValue))
}

return returnValue
} catch is CancellationError {
if flags.contains(.cacheErrors) {
_send(_enclosingInstance: object, .initial)
_send(object, .initial)
}
throw CancellationError()
} catch {
if flags.contains(.cacheErrors) {
_send(_enclosingInstance: object, .failure(error))
_send(object, .failure(error))
}
await object.connectionDelegate?.logger
.trace(
Expand Down Expand Up @@ -391,7 +386,7 @@ public struct OcaProperty<Value: Codable & Sendable>: Codable, Sendable,
)

// if we're not expecting an event, then be sure to update it here
_send(_enclosingInstance: object, .success(value))
_send(object, .success(value))
}
}

Expand All @@ -400,7 +395,7 @@ public struct OcaProperty<Value: Codable & Sendable>: Codable, Sendable,
}

public func refresh(_ object: OcaRoot) async {
_send(_enclosingInstance: object, .initial)
_send(object, .initial)
}

func onEvent(_ object: OcaRoot, event: OcaEvent, eventData data: Data) throws {
Expand All @@ -427,7 +422,7 @@ public struct OcaProperty<Value: Codable & Sendable>: Codable, Sendable,
}
fallthrough
case .currentChanged:
_send(_enclosingInstance: object, .success(eventData.propertyValue))
_send(object, .success(eventData.propertyValue))
default:
throw Ocp1Error.unhandledEvent
}
Expand Down Expand Up @@ -507,23 +502,3 @@ extension OcaProperty.PropertyValue: Hashable where Value: Hashable & Codable {
}
}
}

#if canImport(SwiftUI)
public extension OcaProperty {
var binding: Binding<PropertyValue> {
Binding(
get: {
if let object {
_get(_enclosingInstance: object)
} else {
.initial
}
},
set: {
guard let object else { return }
_set(_enclosingInstance: object, $0)
}
)
}
}
#endif
32 changes: 5 additions & 27 deletions Sources/SwiftOCA/OCC/PropertyTypes/VectorProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,14 @@ public struct OcaVectorProperty<
storage storageKeyPath: ReferenceWritableKeyPath<T, Self>
) -> PropertyValue {
get {
#if canImport(SwiftUI)
object[keyPath: storageKeyPath]._storage._referenceObject(_enclosingInstance: object)
#endif
object[keyPath: storageKeyPath]._storage._registerObservable(
_enclosingInstance: object,
wrapped: wrappedKeyPath
)
return object[keyPath: storageKeyPath]._storage
._get(_enclosingInstance: object)
}
set {
#if canImport(SwiftUI)
object[keyPath: storageKeyPath]._storage._referenceObject(_enclosingInstance: object)
#endif
object[keyPath: storageKeyPath]._storage._set(_enclosingInstance: object, newValue)
}
}
Expand Down Expand Up @@ -152,7 +150,7 @@ public struct OcaVectorProperty<
xy.x = subjectValue.x
xy.y = eventData.propertyValue
}
_storage._send(_enclosingInstance: object, .success(xy))
_storage._send(object, .success(xy))
default:
throw Ocp1Error.unhandledEvent
}
Expand Down Expand Up @@ -188,23 +186,3 @@ public struct OcaVectorProperty<
throw Ocp1Error.notImplemented
}
}

#if canImport(SwiftUI)
public extension OcaVectorProperty {
var binding: Binding<PropertyValue> {
Binding(
get: {
if let object = _storage.object {
_storage._get(_enclosingInstance: object)
} else {
.initial
}
},
set: {
guard let object = _storage.object else { return }
_storage._set(_enclosingInstance: object, $0)
}
)
}
}
#endif
Loading

0 comments on commit 34af3ce

Please sign in to comment.