Skip to content

Commit 053fec0

Browse files
committed
Add ObservationRegistrar logic
1 parent 8353e0e commit 053fec0

File tree

9 files changed

+148
-13
lines changed

9 files changed

+148
-13
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// ObservableProperties.swift
3+
// KMPObservableViewModelCore
4+
//
5+
// Created by Rick Clephas on 25/10/2025.
6+
//
7+
8+
import Observation
9+
import KMPObservableViewModelCoreObjC
10+
11+
/// Helper object that maps any external `Property` to an `ObservableProperty`.
12+
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
13+
internal final class ObservableProperties {
14+
15+
private var properties: [ObjectIdentifier:ObservableProperty] = [:]
16+
17+
private func observableProperty(_ property: any Property) -> ObservableProperty {
18+
let identifier = ObjectIdentifier(property)
19+
if let observableProperty = properties[identifier] { return observableProperty }
20+
let observableProperty = ObservableProperty(property)
21+
properties[identifier] = observableProperty
22+
return observableProperty
23+
}
24+
25+
func access(_ property: any Property) {
26+
observableProperty(property).access()
27+
}
28+
29+
func willSet(_ property: any Property) {
30+
observableProperty(property).willSet()
31+
}
32+
33+
func didSet(_ property: any Property) {
34+
observableProperty(property).didSet()
35+
}
36+
}
37+
38+
/// Helper object that turns an external `Property` into a Swift observable property.
39+
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
40+
private final class ObservableProperty: Observable {
41+
42+
private let registrar = ObservationRegistrar()
43+
private let property: any Property
44+
45+
init(_ property: any Property) {
46+
self.property = property
47+
}
48+
49+
var value: Any? { property.value }
50+
51+
func access() {
52+
registrar.access(self, keyPath: \.value)
53+
}
54+
55+
func willSet() {
56+
registrar.willSet(self, keyPath: \.value)
57+
}
58+
59+
func didSet() {
60+
registrar.didSet(self, keyPath: \.value)
61+
}
62+
}

KMPObservableViewModelCore/ObservableViewModelPublisher.swift

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,43 @@ public final class ObservableViewModelPublisher: Combine.Publisher, KMPObservabl
1818
private let publisher: ObservableObjectPublisher
1919
private let subscriptionCount: any SubscriptionCount
2020

21+
private var _observableProperties: Any? = nil
22+
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
23+
private var observableProperties: ObservableProperties? {
24+
_observableProperties as! ObservableProperties?
25+
}
26+
2127
internal init(_ viewModel: any ViewModel) {
2228
self.publisher = viewModel.objectWillChange
2329
self.subscriptionCount = viewModel.viewModelScope.subscriptionCount
30+
if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *), viewModel is Observable {
31+
_observableProperties = ObservableProperties()
32+
}
2433
}
2534

2635
public func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Void == S.Input {
2736
subscriptionCount.increase()
2837
publisher.receive(subscriber: ObservableViewModelSubscriber(subscriptionCount, subscriber))
2938
}
3039

31-
public func send() {
32-
publisher.send()
40+
public func access(_ property: any Property) {
41+
if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *), let observableProperties {
42+
observableProperties.access(property)
43+
}
44+
}
45+
46+
public func willSet(_ property: any Property) {
47+
if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *), let observableProperties {
48+
observableProperties.willSet(property)
49+
} else {
50+
publisher.send()
51+
}
52+
}
53+
54+
public func didSet(_ property: any Property) {
55+
if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *), let observableProperties {
56+
observableProperties.didSet(property)
57+
}
3358
}
3459
}
3560

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// KMPOVMProperty.h
3+
// KMPObservableViewModelCoreObjC
4+
//
5+
// Created by Rick Clephas on 25/10/2025.
6+
//
7+
8+
#ifndef KMPOVMProperty_h
9+
#define KMPOVMProperty_h
10+
11+
#import <Foundation/Foundation.h>
12+
13+
NS_ASSUME_NONNULL_BEGIN
14+
15+
__attribute__((swift_name("Property")))
16+
@protocol KMPOVMProperty
17+
@property (readonly) id _Nullable value;
18+
@end
19+
20+
NS_ASSUME_NONNULL_END
21+
22+
#endif /* KMPOVMProperty_h */

KMPObservableViewModelCoreObjC/KMPOVMPublisher.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99
#define KMPOVMPublisher_h
1010

1111
#import <Foundation/Foundation.h>
12+
#import "KMPOVMProperty.h"
1213

1314
NS_ASSUME_NONNULL_BEGIN
1415

1516
__attribute__((swift_name("Publisher")))
1617
@protocol KMPOVMPublisher
17-
- (void)send;
18+
- (void)access:(id<KMPOVMProperty>)property;
19+
- (void)willSet:(id<KMPOVMProperty>)property;
20+
- (void)didSet:(id<KMPOVMProperty>)property;
1821
@end
1922

2023
NS_ASSUME_NONNULL_END

KMPObservableViewModelCoreObjC/KMPObservableViewModelCoreObjC.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created by Rick Clephas on 27/11/2022.
66
//
77

8+
#import "KMPOVMProperty.h"
89
#import "KMPOVMPublisher.h"
910
#import "KMPOVMSubscriptionCount.h"
1011
#import "KMPOVMViewModelScope.h"

kmp-observableviewmodel-core/src/appleMain/kotlin/com/rickclephas/kmp/observableviewmodel/ObservableStateFlow.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ internal class ObservableMutableStateFlow<T>(
1717
private val stateFlow: MutableStateFlow<T>
1818
): MutableStateFlow<T> {
1919

20+
private val property = StateFlowProperty(stateFlow)
21+
2022
override var value: T
21-
get() = stateFlow.value
23+
get() {
24+
viewModelScope.publisher?.access(property)
25+
return stateFlow.value
26+
}
2227
set(value) {
23-
if (stateFlow.value != value) {
24-
viewModelScope.publisher?.send()
25-
}
28+
val publisher = viewModelScope.publisher?.takeIf { stateFlow.value != value }
29+
publisher?.willSet(property)
2630
stateFlow.value = value
31+
publisher?.didSet(property)
2732
}
2833

2934
override val replayCache: List<T>
@@ -38,10 +43,11 @@ internal class ObservableMutableStateFlow<T>(
3843
stateFlow.collect(collector)
3944

4045
override fun compareAndSet(expect: T, update: T): Boolean {
41-
if (stateFlow.value == expect && expect != update) {
42-
viewModelScope.publisher?.send()
43-
}
44-
return stateFlow.compareAndSet(expect, update)
46+
val publisher = viewModelScope.publisher?.takeIf { stateFlow.value == expect && expect != update }
47+
publisher?.willSet(property)
48+
val result = stateFlow.compareAndSet(expect, update)
49+
publisher?.didSet(property)
50+
return result
4551
}
4652

4753
@ExperimentalCoroutinesApi
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.rickclephas.kmp.observableviewmodel
2+
3+
import com.rickclephas.kmp.observableviewmodel.objc.KMPOVMPropertyProtocol
4+
import kotlinx.coroutines.flow.StateFlow
5+
import platform.darwin.NSObject
6+
7+
/**
8+
* A [Property][KMPOVMPropertyProtocol] that retrieves its value from a [StateFlow].
9+
*/
10+
internal class StateFlowProperty<out T>(
11+
private val stateFlow: StateFlow<T>
12+
): NSObject(), KMPOVMPropertyProtocol {
13+
override fun value(): T = stateFlow.value
14+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
language = Objective-C
22
package = com.rickclephas.kmp.observableviewmodel.objc
3-
headers = KMPOVMPublisher.h KMPOVMSubscriptionCount.h KMPOVMViewModelScope.h
3+
headers = KMPOVMProperty.h KMPOVMPublisher.h KMPOVMSubscriptionCount.h KMPOVMViewModelScope.h

sample/iosApp/KMPObservableViewModelSample/KMPObservableViewModel.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@
88
import KMPObservableViewModelCore
99
import KMPObservableViewModelSampleShared
1010

11-
extension Kmp_observableviewmodel_coreViewModel: ViewModel { }
11+
extension Kmp_observableviewmodel_coreViewModel: @retroactive ViewModel { }
12+
13+
extension Kmp_observableviewmodel_coreViewModel: @retroactive Observable { }

0 commit comments

Comments
 (0)