Last active
March 31, 2024 05:38
-
-
Save SmartJSONEditor/33d796e81554205fd491b6449376c426 to your computer and use it in GitHub Desktop.
Observation withObservationTracking as Combine publisher
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
import Foundation | |
import Observation | |
// A: Using specific class | |
/// Class that wraps withObservationTracking function into a Combine compatible continuos stream publisher | |
public class ObservationTracker<O: Observable & AnyObject, T> { | |
/// Subscribe to a publisher. | |
public var valuePublisher: AnyPublisher<T, Never> { | |
subject.eraseToAnyPublisher() | |
} | |
private var subject = PassthroughSubject<T, Never>() | |
/// Public init, init with Observable object and its keyPath the publisher should post value changes. | |
public init(_ object: O, keyPath: KeyPath<O, T>) { | |
scheduleObservationTracking(object: object, keyPath: keyPath) | |
} | |
private func scheduleObservationTracking(object: O, keyPath: KeyPath<O, T>) { | |
_ = withObservationTracking { [weak mainObject = object] in | |
mainObject?[keyPath: keyPath] | |
} onChange: { | |
Task { @MainActor [weak self, weak mainObject = object] in | |
guard let self = self else { return } | |
guard let strongObject = mainObject else { return } | |
self.subject.send(strongObject[keyPath: keyPath]) | |
self.scheduleObservationTracking(object: strongObject, keyPath: keyPath) | |
} | |
} | |
} | |
} | |
// B: using Custom publisher | |
extension Observable { | |
public func publisher<O: Observable & AnyObject, T>(keyPath: KeyPath<O, T>) -> AnyPublisher<T, Never> { | |
return ObservablePublisher(object: self as! O, keyPath: keyPath).eraseToAnyPublisher() | |
} | |
} | |
struct ObservablePublisher<Output, Object: Observable & AnyObject>: Publisher { | |
typealias Failure = Never | |
var object: Object | |
var keyPath: KeyPath<Object, Output> | |
func receive<S: Subscriber>( | |
subscriber: S | |
) where S.Input == Output, S.Failure == Never { | |
let subscription = Subscription(publisher: self, target: subscriber) | |
subscriber.receive(subscription: subscription) | |
} | |
} | |
private extension ObservablePublisher { | |
class Subscription<Target: Subscriber>: Combine.Subscription | |
where Target.Input == Output { | |
private let publisher: ObservablePublisher | |
private var target: Target? | |
init(publisher: ObservablePublisher, target: Target) { | |
self.publisher = publisher | |
self.target = target | |
} | |
func request(_ demand: Subscribers.Demand) { | |
if let target = target { | |
scheduleObservationTracking(target: target, object: publisher.object, keyPath: publisher.keyPath) | |
} | |
} | |
private func scheduleObservationTracking(target: Target, object: Object, keyPath: KeyPath<Object, Output>) { | |
_ = withObservationTracking { [weak mainObject = object] in | |
mainObject?[keyPath: keyPath] | |
} onChange: { | |
Task { @MainActor [weak self, weak mainObject = object] in | |
guard let self = self else { return } | |
guard let strongObject = mainObject else { return } | |
_ = target.receive(strongObject[keyPath: keyPath]) | |
self.scheduleObservationTracking(target: target, object: strongObject, keyPath: keyPath) | |
} | |
} | |
} | |
func cancel() { | |
target = nil | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi. I tried to use this in a ViewModel with SwiftUI, but the view keeps redrawing continuously - any idea why?