Skip to content

Commit f5ad567

Browse files
committed
Store ObservableViewModelPublisher inside NativeViewModelScope
1 parent 2dc37a2 commit f5ad567

File tree

10 files changed

+61
-40
lines changed

10 files changed

+61
-40
lines changed

KMPObservableViewModelCore/ObservableViewModel.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import KMPObservableViewModelCoreObjC
1313
public func observableViewModel<VM: ViewModel>(
1414
for viewModel: VM
1515
) -> ObservableViewModel<VM> {
16-
let publishers = observableViewModelPublishers(for: viewModel)
17-
return ObservableViewModel(publishers, viewModel)
16+
return ObservableViewModel(viewModel)
1817
}
1918

2019
/// Gets an `ObservableObject` for the specified `ViewModel`.
@@ -35,13 +34,9 @@ public final class ObservableViewModel<VM: ViewModel>: ObservableObject, Hashabl
3534
/// The observed `ViewModel`.
3635
public let viewModel: VM
3736

38-
/// Holds a strong reference to the publishers
39-
private let publishers: ObservableViewModelPublishers
40-
41-
internal init(_ publishers: ObservableViewModelPublishers, _ viewModel: VM) {
42-
objectWillChange = publishers.publisher
37+
internal init(_ viewModel: VM) {
38+
objectWillChange = viewModel.viewModelWillChange
4339
self.viewModel = viewModel
44-
self.publishers = publishers
4540
}
4641

4742
public static func == (lhs: ObservableViewModel<VM>, rhs: ObservableViewModel<VM>) -> Bool {

KMPObservableViewModelCore/ObservableViewModelPublisher.swift

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,30 @@ import Combine
99
import KMPObservableViewModelCoreObjC
1010

1111
/// Publisher for `ObservableViewModel` that connects to the `ViewModelScope`.
12-
public final class ObservableViewModelPublisher: Publisher {
12+
public final class ObservableViewModelPublisher: Combine.Publisher, KMPObservableViewModelCoreObjC.Publisher {
1313
public typealias Output = Void
1414
public typealias Failure = Never
1515

1616
internal weak var viewModel: (any ViewModel)?
17-
private let subscriptionCount: any SubscriptionCount
1817

19-
private let publisher = ObservableObjectPublisher()
20-
private var objectWillChangeCancellable: AnyCancellable? = nil
18+
private let publisher: ObservableObjectPublisher
19+
private let subscriptionCount: any SubscriptionCount
2120

22-
internal init(_ viewModel: any ViewModel, _ objectWillChange: ObservableObjectPublisher) {
21+
internal init(_ viewModel: any ViewModel) {
2322
self.viewModel = viewModel
23+
self.publisher = viewModel.objectWillChange
2424
self.subscriptionCount = viewModel.viewModelScope.subscriptionCount
25-
viewModel.viewModelScope.setSendObjectWillChange { [weak self] in
26-
self?.publisher.send()
27-
}
28-
objectWillChangeCancellable = objectWillChange.sink { [weak self] _ in
29-
self?.publisher.send()
30-
}
3125
}
3226

3327
public func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Void == S.Input {
3428
subscriptionCount.increase()
3529
publisher.receive(subscriber: ObservableViewModelSubscriber(subscriptionCount, subscriber))
3630
}
3731

32+
public func send() {
33+
publisher.send()
34+
}
35+
3836
deinit {
3937
guard let viewModel else { return }
4038
if let cancellable = viewModel as? Cancellable {

KMPObservableViewModelCore/ObservableViewModelPublishers.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ internal func observableViewModelPublishers<VM: ViewModel>(
2626
fatalError("ObservableViewModel has been deallocated")
2727
}()
2828
} else {
29-
let publisher = ObservableViewModelPublisher(viewModel, viewModel.objectWillChange)
29+
let publisher = ObservableViewModelPublisher(viewModel)
3030
publishers = ObservableViewModelPublishers(publisher)
3131
let object = WeakObservableViewModelPublishers(publishers)
3232
objc_setAssociatedObject(viewModel, &observableViewModelPublishersKey, object, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

KMPObservableViewModelCore/ViewModel.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,20 @@ import KMPObservableViewModelCoreObjC
1212
public protocol ViewModel: ObservableObject where ObjectWillChangePublisher == ObservableObjectPublisher {
1313
/// The `ViewModelScope` of this `ViewModel`.
1414
var viewModelScope: ViewModelScope { get }
15+
/// An `ObservableViewModelPublisher` that emits before this `ViewModel` has changed.
16+
var viewModelWillChange: ObservableViewModelPublisher { get }
1517
/// Internal KMP-ObservableViewModel function used to clear the ViewModel.
1618
/// - Warning: You should NOT call this yourself!
1719
func clear()
1820
}
21+
22+
public extension ViewModel {
23+
var viewModelWillChange: ObservableViewModelPublisher {
24+
if let publisher = viewModelScope.publisher {
25+
return publisher as! ObservableViewModelPublisher
26+
}
27+
let publisher = ObservableViewModelPublisher(self)
28+
viewModelScope.publisher = publisher
29+
return publisher
30+
}
31+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// KMPOVMPublisher.h
3+
// KMPObservableViewModel
4+
//
5+
// Created by Rick Clephas on 09/06/2025.
6+
//
7+
8+
#ifndef KMPOVMPublisher_h
9+
#define KMPOVMPublisher_h
10+
11+
#import <Foundation/Foundation.h>
12+
13+
NS_ASSUME_NONNULL_BEGIN
14+
15+
__attribute__((swift_name("Publisher")))
16+
@protocol KMPOVMPublisher
17+
- (void)send;
18+
@end
19+
20+
NS_ASSUME_NONNULL_END
21+
22+
#endif /* KMPOVMPublisher_h */

KMPObservableViewModelCoreObjC/KMPOVMViewModelScope.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
#define KMPOVMViewModelScope_h
1010

1111
#import <Foundation/Foundation.h>
12+
#import "KMPOVMPublisher.h"
1213
#import "KMPOVMSubscriptionCount.h"
1314

1415
NS_ASSUME_NONNULL_BEGIN
1516

1617
__attribute__((swift_name("ViewModelScope")))
1718
@protocol KMPOVMViewModelScope
1819
@property (readonly) id<KMPOVMSubscriptionCount> subscriptionCount;
19-
20-
- (void)setSendObjectWillChange:(void (^ _Nonnull)(void))sendObjectWillChange;
20+
@property id<KMPOVMPublisher> _Nullable publisher;
2121
@end
2222

2323
NS_ASSUME_NONNULL_END

KMPObservableViewModelCoreObjC/KMPObservableViewModelCoreObjC.h

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

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

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

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.rickclephas.kmp.observableviewmodel
22

3+
import com.rickclephas.kmp.observableviewmodel.objc.KMPOVMPublisherProtocol
34
import kotlinx.coroutines.CoroutineScope
45
import platform.darwin.NSObject
56

@@ -14,20 +15,11 @@ internal class NativeViewModelScope internal constructor(
1415
private val _subscriptionCount = SubscriptionCount()
1516
override fun subscriptionCount(): SubscriptionCount = _subscriptionCount
1617

17-
private var sendObjectWillChange: (() -> Unit)? = null
18-
19-
override fun setSendObjectWillChange(sendObjectWillChange: () -> Unit) {
20-
if (this.sendObjectWillChange != null) {
21-
throw IllegalStateException("ViewModel can't be wrapped more than once")
22-
}
23-
this.sendObjectWillChange = sendObjectWillChange
24-
}
25-
26-
/**
27-
* Invokes the object will change listener set by [setSendObjectWillChange].
28-
*/
29-
fun sendObjectWillChange() {
30-
sendObjectWillChange?.invoke()
18+
private var _publisher: KMPOVMPublisherProtocol? = null
19+
override fun publisher(): KMPOVMPublisherProtocol? = _publisher
20+
override fun setPublisher(publisher: KMPOVMPublisherProtocol?) {
21+
if (_publisher != null) throw IllegalStateException("ViewModel can't be initialized more than once")
22+
_publisher = publisher
3123
}
3224
}
3325

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
88
import kotlinx.coroutines.flow.StateFlow
99

1010
/**
11-
* A [MutableStateFlow] that triggers [NativeViewModelScope.sendObjectWillChange]
11+
* A [MutableStateFlow] that triggers [NativeViewModelScope.publisher]
1212
* and accounts for the [NativeViewModelScope.subscriptionCount].
1313
*/
1414
@OptIn(ExperimentalForInheritanceCoroutinesApi::class)
@@ -21,7 +21,7 @@ internal class ObservableMutableStateFlow<T>(
2121
get() = stateFlow.value
2222
set(value) {
2323
if (stateFlow.value != value) {
24-
viewModelScope.sendObjectWillChange()
24+
viewModelScope.publisher?.send()
2525
}
2626
stateFlow.value = value
2727
}
@@ -36,7 +36,7 @@ internal class ObservableMutableStateFlow<T>(
3636

3737
override fun compareAndSet(expect: T, update: T): Boolean {
3838
if (stateFlow.value == expect && expect != update) {
39-
viewModelScope.sendObjectWillChange()
39+
viewModelScope.publisher?.send()
4040
}
4141
return stateFlow.compareAndSet(expect, update)
4242
}
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 = KMPOVMSubscriptionCount.h KMPOVMViewModelScope.h
3+
headers = KMPOVMPublisher.h KMPOVMSubscriptionCount.h KMPOVMViewModelScope.h

0 commit comments

Comments
 (0)