Skip to content

Commit 8140c4d

Browse files
authored
feat: View models now update on the MainActor (#76)
1 parent 8e7a09e commit 8140c4d

File tree

13 files changed

+213
-295
lines changed

13 files changed

+213
-295
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22
# Parse-Swift Changelog
33

44
### main
5-
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.1.1...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift)
5+
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.2.0...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift)
66
* _Contributing to this repo? Add info about your change here to be included in the next release_
77

8+
### 5.2.0
9+
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.1.1...5.2.0), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.2.0/documentation/parseswift)
10+
11+
__New features__
12+
* All Parse-Swift view models now update on the MainActor to make view changes more predictable ([#75](https://github.com/netreconlab/Parse-Swift/pull/75)), thanks to [Corey Baker](https://github.com/cbaker6).
13+
814
### 5.1.1
915
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.1.0...5.1.1), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.1.1/documentation/parseswift)
1016

ParseSwift.playground/Pages/17 - SwiftUI - Finding Objects.xcplaygroundpage/Contents.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ struct ContentView: View {
9595
TextField("Name", text: $name)
9696
TextField("Points", text: $points)
9797
Button(action: {
98+
//: Code below should normally be in a ViewModel
99+
//: This is just a simple example...
98100
guard let pointsValue = Int(points),
99101
let linkToFile = URL(string: "https://parseplatform.org/img/logo.svg") else {
100102
return
@@ -111,7 +113,9 @@ struct ContentView: View {
111113
switch result {
112114
case .success:
113115
savedLabel = "Saved score"
114-
self.viewModel.find()
116+
Task {
117+
await self.viewModel.find()
118+
}
115119
case .failure(let error):
116120
savedLabel = "Error: \(error.message)"
117121
}
@@ -136,14 +140,14 @@ struct ContentView: View {
136140
}
137141
}
138142
Spacer()
139-
}.onAppear(perform: {
140-
viewModel.find()
141-
}).alert(isPresented: $isShowingAction, content: {
143+
}.task {
144+
await viewModel.find()
145+
}.alert(isPresented: $isShowingAction) {
142146
Alert(title: Text("GameScore"),
143147
message: Text(savedLabel),
144148
dismissButton: .default(Text("Ok"), action: {
145149
}))
146-
})
150+
}
147151
}
148152
}
149153

ParseSwift.playground/Pages/19 - SwiftUI - LiveQuery.xcplaygroundpage/Contents.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ struct ContentView: View {
7575
var body: some View {
7676
VStack {
7777

78-
if subscription.subscribed != nil {
78+
if subscription.isSubscribed {
7979
Text("Subscribed to query!")
80-
} else if subscription.unsubscribed != nil {
80+
} else if subscription.isUnsubscribed {
8181
Text("Unsubscribed from query!")
8282
} else if let event = subscription.event {
8383

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,6 @@ You are not limited to a single Live Query Client - you can create multiple inst
259259
## Migrating from Older Versions and SDKs
260260

261261
1. See the [discussion](https://github.com/netreconlab/Parse-Swift/discussions/74) to learn how to migrate from Parse-Swift<sup>OG</sup> 4.15.0+ to 5.1.1+
262-
1. See the [discussion](https://github.com/netreconlab/Parse-Swift/discussions/70) to learn how to migrate from the [Parse-Swift](https://github.com/parse-community/Parse-Swift)
263-
1. See the [discussion](https://github.com/netreconlab/Parse-Swift/discussions/71) to learn how to migrate from the [Parse-SDK-iOS-OSX](https://github.com/parse-community/Parse-SDK-iOS-OSX)
262+
1. See the [discussion](https://github.com/netreconlab/Parse-Swift/discussions/70) to learn how to migrate from [parse-community/Parse-Swift](https://github.com/parse-community/Parse-Swift)
263+
1. See the [discussion](https://github.com/netreconlab/Parse-Swift/discussions/71) to learn how to migrate from [Parse-SDK-iOS-OSX](https://github.com/parse-community/Parse-SDK-iOS-OSX)
264264

Sources/ParseSwift/LiveQuery/Subscription.swift

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,35 +25,41 @@ open class Subscription<T: ParseObject>: QueryViewModel<T>, QuerySubscribable {
2525
if newValue != nil {
2626
subscribed = nil
2727
unsubscribed = nil
28-
DispatchQueue.main.async {
29-
self.objectWillChange.send()
30-
}
28+
self.objectWillChange.send()
3129
}
3230
}
3331
}
3432

33+
/// If **true** the LiveQuery subscription is currently active,
34+
/// **false** otherwise.
35+
open var isSubscribed: Bool {
36+
subscribed != nil
37+
}
38+
3539
/// Updates and notifies when a subscription request has been fulfilled and if it is new.
3640
open var subscribed: (query: Query<T>, isNew: Bool)? {
3741
willSet {
3842
if newValue != nil {
3943
unsubscribed = nil
4044
event = nil
41-
DispatchQueue.main.async {
42-
self.objectWillChange.send()
43-
}
45+
self.objectWillChange.send()
4446
}
4547
}
4648
}
4749

50+
/// If **true** the LiveQuery subscription is currently inactive,
51+
/// **false** otherwise.
52+
open var isUnsubscribed: Bool {
53+
unsubscribed != nil
54+
}
55+
4856
/// Updates and notifies when an unsubscribe request has been fulfilled.
4957
open var unsubscribed: Query<T>? {
5058
willSet {
5159
if newValue != nil {
5260
subscribed = nil
5361
event = nil
54-
DispatchQueue.main.async {
55-
self.objectWillChange.send()
56-
}
62+
self.objectWillChange.send()
5763
}
5864
}
5965
}
@@ -69,7 +75,7 @@ open class Subscription<T: ParseObject>: QueryViewModel<T>, QuerySubscribable {
6975
}
7076

7177
// MARK: QuerySubscribable
72-
78+
@MainActor
7379
open func didReceive(_ eventData: Data) throws {
7480
// Need to decode the event with respect to the `ParseObject`.
7581
let eventMessage = try ParseCoding.jsonDecoder().decode(EventResponse<T>.self, from: eventData)
@@ -79,10 +85,12 @@ open class Subscription<T: ParseObject>: QueryViewModel<T>, QuerySubscribable {
7985
self.event = (query, event)
8086
}
8187

88+
@MainActor
8289
open func didSubscribe(_ new: Bool) {
8390
self.subscribed = (query, new)
8491
}
8592

93+
@MainActor
8694
open func didUnsubscribe() {
8795
self.unsubscribed = query
8896
}

Sources/ParseSwift/ParseConstants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Foundation
1010

1111
enum ParseConstants {
1212
static let sdk = "swift"
13-
static let version = "5.1.1"
13+
static let version = "5.2.1"
1414
static let fileManagementDirectory = "parse/"
1515
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
1616
static let fileManagementLibraryDirectory = "Library/"

Sources/ParseSwift/Protocols/CloudObservable.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Created by Corey Baker on 7/11/21.
66
// Copyright © 2021 Network Reconnaissance Lab. All rights reserved.
77
//
8-
#if canImport(SwiftUI)
8+
#if canImport(Combine)
99
import Foundation
1010

1111
/**
@@ -27,12 +27,14 @@ public protocol CloudObservable: ObservableObject {
2727
when the result of its execution.
2828
- parameter options: A set of header options sent to the server. Defaults to an empty set.
2929
*/
30-
func runFunction(options: API.Options)
30+
@MainActor
31+
func runFunction(options: API.Options) async
3132

3233
/**
3334
Starts a Cloud Code Job *asynchronously* and updates the view model with the result and jobStatusId of the job.
3435
- parameter options: A set of header options sent to the server. Defaults to an empty set.
3536
*/
36-
func startJob(options: API.Options)
37+
@MainActor
38+
func startJob(options: API.Options) async
3739
}
3840
#endif

Sources/ParseSwift/Protocols/QueryObservable.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// Copyright © 2021 Network Reconnaissance Lab. All rights reserved.
77
//
88

9-
#if canImport(SwiftUI)
9+
#if canImport(Combine)
1010
import Foundation
1111

1212
/**
@@ -32,7 +32,8 @@ public protocol QueryObservable: ObservableObject {
3232

3333
- parameter options: A set of header options sent to the server. Defaults to an empty set.
3434
*/
35-
func find(options: API.Options)
35+
@MainActor
36+
func find(options: API.Options) async
3637

3738
/**
3839
Retrieves *asynchronously* a complete list of `ParseObject`'s that satisfy this query
@@ -42,24 +43,27 @@ public protocol QueryObservable: ObservableObject {
4243
- warning: The items are processed in an unspecified order. The query may not have any sort
4344
order, and may not use limit or skip.
4445
*/
46+
@MainActor
4547
func findAll(batchLimit: Int?,
46-
options: API.Options)
48+
options: API.Options) async
4749

4850
/**
4951
Gets an object *asynchronously* and updates the view model when complete.
5052

5153
- warning: This method mutates the query. It will reset the limit to `1`.
5254
- parameter options: A set of header options sent to the server. Defaults to an empty set.
5355
*/
54-
func first(options: API.Options)
56+
@MainActor
57+
func first(options: API.Options) async
5558

5659
/**
5760
Counts objects *synchronously* based on the constructed query and updates the view model
5861
when complete.
5962

6063
- parameter options: A set of header options sent to the server. Defaults to an empty set.
6164
*/
62-
func count(options: API.Options)
65+
@MainActor
66+
func count(options: API.Options) async
6367

6468
/**
6569
Executes an aggregate query *asynchronously* and updates the view model when complete.
@@ -70,7 +74,8 @@ public protocol QueryObservable: ObservableObject {
7074
- parameter options: A set of header options sent to the server. Defaults to an empty set.
7175
- warning: This has not been tested thoroughly.
7276
*/
77+
@MainActor
7378
func aggregate(_ pipeline: [[String: Encodable]],
74-
options: API.Options)
79+
options: API.Options) async
7580
}
7681
#endif

Sources/ParseSwift/Types/CloudViewModel.swift

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Created by Corey Baker on 7/11/21.
66
// Copyright © 2021 Network Reconnaissance Lab. All rights reserved.
77
//
8-
#if canImport(SwiftUI)
8+
#if canImport(Combine)
99
import Foundation
1010

1111
/**
@@ -24,9 +24,7 @@ open class CloudViewModel<T: ParseCloudable>: CloudObservable {
2424
willSet {
2525
if newValue != nil {
2626
self.error = nil
27-
DispatchQueue.main.async {
28-
self.objectWillChange.send()
29-
}
27+
self.objectWillChange.send()
3028
}
3129
}
3230
}
@@ -36,9 +34,7 @@ open class CloudViewModel<T: ParseCloudable>: CloudObservable {
3634
willSet {
3735
if newValue != nil {
3836
self.results = nil
39-
DispatchQueue.main.async {
40-
self.objectWillChange.send()
41-
}
37+
self.objectWillChange.send()
4238
}
4339
}
4440
}
@@ -47,27 +43,21 @@ open class CloudViewModel<T: ParseCloudable>: CloudObservable {
4743
self.cloudCode = cloudCode
4844
}
4945

50-
public func runFunction(options: API.Options = []) {
51-
cloudCode.runFunction(options: options) { results in
52-
switch results {
53-
54-
case .success(let results):
55-
self.results = results
56-
case .failure(let error):
57-
self.error = error
58-
}
46+
@MainActor
47+
public func runFunction(options: API.Options = []) async {
48+
do {
49+
self.results = try await cloudCode.runFunction(options: options)
50+
} catch {
51+
self.error = error as? ParseError ?? ParseError(swift: error)
5952
}
6053
}
6154

62-
public func startJob(options: API.Options = []) {
63-
cloudCode.startJob(options: options) { results in
64-
switch results {
65-
66-
case .success(let results):
67-
self.results = results
68-
case .failure(let error):
69-
self.error = error
70-
}
55+
@MainActor
56+
public func startJob(options: API.Options = []) async {
57+
do {
58+
self.results = try await cloudCode.startJob(options: options)
59+
} catch {
60+
self.error = error as? ParseError ?? ParseError(swift: error)
7161
}
7262
}
7363
}

0 commit comments

Comments
 (0)