Skip to content

Commit 3720dcb

Browse files
committed
Merge branch 'master' into feature/coroutines-1.8.0
2 parents 961da87 + 4a1389c commit 3720dcb

10 files changed

+71
-23
lines changed

.github/workflows/release-swift.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
- name: Setup Xcode
1414
uses: maxim-lobanov/setup-xcode@v1
1515
with:
16-
xcode-version: '13.4.1'
16+
xcode-version: '15.0'
1717
- name: Publish KMMViewModelCoreObjC
1818
run: pod trunk push KMMViewModelCoreObjC.podspec --synchronous
1919
env:

KMMViewModelCore.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'KMMViewModelCore'
3-
s.version = '1.0.0-ALPHA-16'
3+
s.version = '1.0.0-ALPHA-18'
44
s.summary = 'Library to share Kotlin ViewModels with Swift'
55

66
s.homepage = 'https://github.com/rickclephas/KMM-ViewModel'

KMMViewModelCore/ObservableViewModelPublisher.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ public final class ObservableViewModelPublisher: Publisher {
1313
public typealias Output = Void
1414
public typealias Failure = Never
1515

16-
internal weak var viewModelScope: ViewModelScope?
16+
internal weak var viewModel: (any KMMViewModel)?
1717

1818
private let publisher = ObservableObjectPublisher()
1919
private var objectWillChangeCancellable: AnyCancellable? = nil
2020

21-
internal init(_ viewModelScope: ViewModelScope, _ objectWillChange: ObservableObjectPublisher) {
22-
self.viewModelScope = viewModelScope
23-
viewModelScope.setSendObjectWillChange { [weak self] in
21+
internal init(_ viewModel: any KMMViewModel, _ objectWillChange: ObservableObjectPublisher) {
22+
self.viewModel = viewModel
23+
viewModel.viewModelScope.setSendObjectWillChange { [weak self] in
2424
self?.publisher.send()
2525
}
2626
objectWillChangeCancellable = objectWillChange.sink { [weak self] _ in
@@ -29,12 +29,16 @@ public final class ObservableViewModelPublisher: Publisher {
2929
}
3030

3131
public func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Void == S.Input {
32-
viewModelScope?.increaseSubscriptionCount()
32+
viewModel?.viewModelScope.increaseSubscriptionCount()
3333
publisher.receive(subscriber: ObservableViewModelSubscriber(self, subscriber))
3434
}
3535

3636
deinit {
37-
viewModelScope?.cancel()
37+
guard let viewModel else { return }
38+
if let cancellable = viewModel as? Cancellable {
39+
cancellable.cancel()
40+
}
41+
viewModel.viewModelScope.cancel()
3842
}
3943
}
4044

@@ -85,6 +89,6 @@ private class ObservableViewModelSubscription: Subscription {
8589
subscription.cancel()
8690
guard !cancelled else { return }
8791
cancelled = true
88-
publisher.viewModelScope?.decreaseSubscriptionCount()
92+
publisher.viewModel?.viewModelScope.decreaseSubscriptionCount()
8993
}
9094
}

KMMViewModelCore/ObservableViewModelPublishers.swift

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

KMMViewModelCoreObjC.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'KMMViewModelCoreObjC'
3-
s.version = '1.0.0-ALPHA-16'
3+
s.version = '1.0.0-ALPHA-18'
44
s.summary = 'Library to share Kotlin ViewModels with Swift'
55

66
s.homepage = 'https://github.com/rickclephas/KMM-ViewModel'

KMMViewModelSwiftUI.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'KMMViewModelSwiftUI'
3-
s.version = '1.0.0-ALPHA-16'
3+
s.version = '1.0.0-ALPHA-18'
44
s.summary = 'Library to share Kotlin ViewModels with SwiftUI'
55

66
s.homepage = 'https://github.com/rickclephas/KMM-ViewModel'

README.md

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ A library that allows you to share ViewModels between Android and iOS.
44

55
## Compatibility
66

7-
The latest version of the library uses Kotlin version `1.9.21`.
7+
The latest version of the library uses Kotlin version `1.9.22`.
88
Compatibility versions for older and/or preview Kotlin versions are also available:
99

1010
| Version | Version suffix | Kotlin | Coroutines | AndroidX Lifecycle |
1111
|----------------|---------------------|:-----------:|:----------:|:------------------:|
12-
| _latest_ | -kotlin-2.0.0-Beta1 | 2.0.0-Beta1 | 1.7.3 | 2.6.2 |
13-
| **_latest_** | **_no suffix_** | **1.9.21** | **1.7.3** | **2.6.2** |
12+
| _latest_ | -kotlin-2.0.0-Beta3 | 2.0.0-Beta3 | 1.8.0-RC2 | 2.6.2 |
13+
| **_latest_** | **_no suffix_** | **1.9.22** | **1.7.3** | **2.6.2** |
14+
| 1.0.0-ALPHA-16 | _no suffix_ | 1.9.21 | 1.7.3 | 2.6.2 |
1415
| 1.0.0-ALPHA-15 | _no suffix_ | 1.9.20 | 1.7.3 | 2.6.2 |
1516
| 1.0.0-ALPHA-14 | _no suffix_ | 1.9.10 | 1.7.3 | 2.6.1 |
1617
| 1.0.0-ALPHA-13 | _no suffix_ | 1.9.0 | 1.7.3 | 2.6.1 |
@@ -33,7 +34,7 @@ kotlin {
3334
}
3435
commonMain {
3536
dependencies {
36-
api("com.rickclephas.kmm:kmm-viewmodel-core:1.0.0-ALPHA-16")
37+
api("com.rickclephas.kmm:kmm-viewmodel-core:1.0.0-ALPHA-18")
3738
}
3839
}
3940
}
@@ -143,7 +144,7 @@ class TimeTravelFragment: Fragment(R.layout.fragment_time_travel) {
143144
Add the Swift package to your `Package.swift` file:
144145
```swift
145146
dependencies: [
146-
.package(url: "https://github.com/rickclephas/KMM-ViewModel.git", from: "1.0.0-ALPHA-16")
147+
.package(url: "https://github.com/rickclephas/KMM-ViewModel.git", from: "1.0.0-ALPHA-18")
147148
]
148149
```
149150

@@ -155,7 +156,7 @@ Or add it in Xcode by going to `File` > `Add Packages...` and providing the URL:
155156

156157
If you like you can also use CocoaPods instead of SPM:
157158
```ruby
158-
pod 'KMMViewModelSwiftUI', '1.0.0-ALPHA-16'
159+
pod 'KMMViewModelSwiftUI', '1.0.0-ALPHA-18'
159160
```
160161
</p>
161162
</details>
@@ -224,3 +225,46 @@ This will prevent your Swift view models from being deallocated too soon.
224225

225226
> [!NOTE]
226227
> For lists, sets and dictionaries containing view models there is `childViewModels(at:)`.
228+
229+
### Cancellable ViewModel
230+
231+
When subclassing your Kotlin ViewModel in Swift you might experience some issues in the way those ViewModels are cleared.
232+
233+
An example of such an issue is when you are using a Combine publisher to observe a Flow through KMP-NativeCoroutines:
234+
```swift
235+
import Combine
236+
import KMPNativeCoroutinesCombine
237+
import shared // This should be your shared KMM module
238+
239+
class TimeTravelViewModel: shared.TimeTravelViewModel {
240+
241+
private var cancellables = Set<AnyCancellable>()
242+
243+
override init() {
244+
super.init()
245+
createPublisher(for: currentTimeFlow)
246+
.assertNoFailure()
247+
.sink { time in print("It's \(time)") }
248+
.store(in: &cancellables)
249+
}
250+
}
251+
```
252+
253+
Since `currentTimeFlow` is a StateFlow we don't ever expect it to fail, which is why we are using the `assertNoFailure`.
254+
However, in this case you'll notice that the publisher will fail with a `JobCancellationException`.
255+
256+
The problem here is that before the `TimeTravelViewModel` is deinited it will already be cleared.
257+
Meaning the `viewModelScope` is cancelled and `onCleared` is called.
258+
This results in the Combine publisher outliving the underlying StateFlow collection.
259+
260+
To solve such issues you should have your Swift ViewModel conform to `Cancellable`
261+
and perform the required cleanup in the `cancel` function:
262+
```swift
263+
class TimeTravelViewModel: shared.TimeTravelViewModel, Cancellable {
264+
func cancel() {
265+
cancellables = []
266+
}
267+
}
268+
```
269+
270+
KMM-ViewModel will make sure to call the `cancel` function before the ViewModel is being cleared.

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ buildscript {
1919

2020
allprojects {
2121
group = "com.rickclephas.kmm"
22-
version = "1.0.0-ALPHA-16"
22+
version = "1.0.0-ALPHA-18"
2323

2424
repositories {
2525
mavenCentral()

gradle/libs.versions.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ android = "7.4.2"
55
androidx-lifecycle = "2.6.2"
66

77
# Sample versions
8-
androidx-compose = "2023.06.01"
9-
androidx-compose-compiler = "1.5.5-dev-k1.9.21-163bb051fe5"
10-
androidx-fragment = "1.6.1"
8+
androidx-compose = "2023.10.01"
9+
androidx-compose-compiler = "1.5.8-dev-k1.9.22-42b6ec2b037"
10+
androidx-fragment = "1.6.2"
1111
ksp = "1.9.22-1.0.16"
1212
nativecoroutines = "1.0.0-ALPHA-24"
1313

sample/androidApp/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ plugins {
77

88
android {
99
namespace = "com.rickclephas.kmm.viewmodel.sample"
10-
compileSdk = 33
10+
compileSdk = 34
1111
defaultConfig {
1212
applicationId = "com.rickclephas.kmm.viewmodel.sample"
1313
minSdk = 28

0 commit comments

Comments
 (0)