Skip to content

Commit 78067b6

Browse files
authored
fix: All view models always run on MainActor (#229)
* fix: All view models always run on MainActor * Bump version number * Update Xcode version in release workflow * Use SPM for oldest tests * add back unchecked Sendable for older Swift builds * Manual main actor for query view models * Update keychain setup in CI workflow
1 parent 6da5ccb commit 78067b6

14 files changed

Lines changed: 89 additions & 38 deletions

.github/workflows/ci.yml

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,12 @@ jobs:
4040
run: brew install swiftlint
4141
- name: Create and set the default keychain
4242
run: |
43-
security create-keychain -p "" temporary
44-
security default-keychain -s temporary
45-
security unlock-keychain -p "" temporary
46-
security set-keychain-settings -lut 7200 temporary
43+
KEYCHAIN_NAME="temporary-${GITHUB_RUN_ID}"
44+
security delete-keychain "$KEYCHAIN_NAME" 2>/dev/null || true
45+
security create-keychain -p "" "$KEYCHAIN_NAME"
46+
security default-keychain -s "$KEYCHAIN_NAME"
47+
security unlock-keychain -p "" "$KEYCHAIN_NAME"
48+
security set-keychain-settings -lut 7200 "$KEYCHAIN_NAME"
4749
- name: Build-Test
4850
run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -workspace Parse.xcworkspace -scheme ParseSwift -derivedDataPath DerivedData -destination ${{ matrix.destination }} ${{ matrix.action }} 2>&1 | xcbeautify --renderer github-actions
4951
env:
@@ -73,10 +75,12 @@ jobs:
7375
- uses: actions/checkout@v6
7476
- name: Create and set the default keychain
7577
run: |
76-
security create-keychain -p "" temporary
77-
security default-keychain -s temporary
78-
security unlock-keychain -p "" temporary
79-
security set-keychain-settings -lut 7200 temporary
78+
KEYCHAIN_NAME="temporary-${GITHUB_RUN_ID}"
79+
security delete-keychain "$KEYCHAIN_NAME" 2>/dev/null || true
80+
security create-keychain -p "" "$KEYCHAIN_NAME"
81+
security default-keychain -s "$KEYCHAIN_NAME"
82+
security unlock-keychain -p "" "$KEYCHAIN_NAME"
83+
security set-keychain-settings -lut 7200 "$KEYCHAIN_NAME"
8084
- name: Build-Test
8185
run: set -o pipefail && env NSUnbufferedIO=YES swift test --enable-code-coverage 2>&1 | xcbeautify --renderer github-actions
8286
env:
@@ -100,18 +104,20 @@ jobs:
100104
env:
101105
DEVELOPER_DIR: ${{ env.CI_XCODE_LATEST }}
102106

103-
xcode-test-oldest:
107+
spm-test-oldest:
104108
timeout-minutes: 25
105109
needs: linux
106110
runs-on: macos-15
107111
steps:
108112
- uses: actions/checkout@v6
109113
- name: Create and set the default keychain
110114
run: |
111-
security create-keychain -p "" temporary
112-
security default-keychain -s temporary
113-
security unlock-keychain -p "" temporary
114-
security set-keychain-settings -lut 7200 temporary
115+
KEYCHAIN_NAME="temporary-${GITHUB_RUN_ID}"
116+
security delete-keychain "$KEYCHAIN_NAME" 2>/dev/null || true
117+
security create-keychain -p "" "$KEYCHAIN_NAME"
118+
security default-keychain -s "$KEYCHAIN_NAME"
119+
security unlock-keychain -p "" "$KEYCHAIN_NAME"
120+
security set-keychain-settings -lut 7200 "$KEYCHAIN_NAME"
115121
- name: Build-Test
116122
run: set -o pipefail && env NSUnbufferedIO=YES swift test --enable-code-coverage 2>&1 | xcbeautify --renderer github-actions
117123
env:
@@ -121,7 +127,7 @@ jobs:
121127
id: coverage-files
122128
with:
123129
format: lcov
124-
search-paths: ./DerivedData
130+
search-paths: ./.build
125131
ignore-conversion-failures: true
126132
env:
127133
DEVELOPER_DIR: ${{ env.CI_XCODE_OLDEST }}
@@ -158,7 +164,7 @@ jobs:
158164
token: ${{ secrets.CODECOV_TOKEN }}
159165

160166
android:
161-
timeout-minutes: 15
167+
timeout-minutes: 10
162168
runs-on: ubuntu-latest
163169
steps:
164170
- uses: actions/checkout@v6

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ on:
33
release:
44
types: [published]
55
env:
6-
CI_XCODE_LATEST: '/Applications/Xcode_26.1.app/Contents/Developer'
6+
CI_XCODE_LATEST: '/Applications/Xcode_26.2.app/Contents/Developer'
77

88
jobs:
99
cocoapods:

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/6.0.1...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift)
5+
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/6.0.3...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+
### 6.0.3
9+
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/6.0.2...6.0.3), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/6.0.3/documentation/parseswift)
10+
11+
__Fixes__
12+
* All view models run on the `@MainActor` ([#229](https://github.com/netreconlab/Parse-Swift/pull/229)), thanks to [Corey Baker](https://github.com/cbaker6).
13+
814
### 6.0.1
915
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/6.0.0...6.0.1), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/6.0.1/documentation/parseswift)
1016

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ import PackageDescription
8080
let package = Package(
8181
name: "YOUR_PROJECT_NAME",
8282
dependencies: [
83-
.package(url: "https://github.com/netreconlab/Parse-Swift", .upToNextMajor(from: "6.0.0"))
83+
.package(url: "https://github.com/netreconlab/Parse-Swift", .upToNextMajor(from: "6.0.3"))
8484
],
8585
targets: [
8686
.target(

Scripts/generate-documentation

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ mv Parse.xcworkspace "$TEMP_WORKSPACE_DIR/Parse.xcworkspace"
104104

105105
xcodebuild clean build -scheme ParseSwift \
106106
-destination generic/platform=iOS \
107-
OTHER_SWIFT_FLAGS="-emit-symbol-graph -emit-symbol-graph-dir '$SGFS_DIR'" | xcpretty
107+
OTHER_SWIFT_FLAGS="-emit-symbol-graph -emit-symbol-graph-dir '$SGFS_DIR'" 2>&1 | xcbeautify
108108

109109
mv "$TEMP_WORKSPACE_DIR/Parse.xcworkspace" ./Parse.xcworkspace
110110
rm -r "$TEMP_WORKSPACE_DIR"

Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ extension ParseLiveQuery {
672672
// MARK: Subscribing
673673
extension ParseLiveQuery {
674674

675+
@MainActor
675676
func subscribe<T>(_ query: Query<T>) async throws -> Subscription<T> {
676677
try await subscribe(Subscription(query: query))
677678
}
@@ -776,6 +777,7 @@ public extension Query {
776777
- returns: The subscription that has just been registered.
777778
- throws: An error of type `ParseError`.
778779
*/
780+
@MainActor
779781
func subscribe(_ client: ParseLiveQuery) async throws -> Subscription<ResultType> {
780782
try await client.subscribe(Subscription(query: self))
781783
}

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 = "6.0.1"
13+
static let version = "6.0.3"
1414
static let fileManagementDirectory = "parse/"
1515
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
1616
static let fileManagementLibraryDirectory = "Library/"

Sources/ParseSwift/Protocols/CloudObservable.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import FoundationNetworking
1515
This protocol describes the interface for creating a view model for `ParseCloud` functions and jobs.
1616
You can use this protocol on any custom class of yours, instead of `CloudViewModel`, if it fits your use case better.
1717
*/
18+
@MainActor
1819
public protocol CloudObservable: ObservableObject {
1920

2021
/// The `ParseObject` associated with this view model.
@@ -30,14 +31,12 @@ public protocol CloudObservable: ObservableObject {
3031
when the result of its execution.
3132
- parameter options: A set of header options sent to the server. Defaults to an empty set.
3233
*/
33-
@MainActor
3434
func runFunction(options: API.Options) async
3535

3636
/**
3737
Starts a Cloud Code Job *asynchronously* and updates the view model with the result and jobStatusId of the job.
3838
- parameter options: A set of header options sent to the server. Defaults to an empty set.
3939
*/
40-
@MainActor
4140
func startJob(options: API.Options) async
4241
}
4342
#endif

Sources/ParseSwift/Protocols/QueryObservable.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public protocol QueryObservable: ObservableObject {
3232

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

3838
/**
@@ -43,7 +43,7 @@ public protocol QueryObservable: ObservableObject {
4343
- warning: The items are processed in an unspecified order. The query may not have any sort
4444
order, and may not use limit or skip.
4545
*/
46-
@MainActor
46+
@MainActor
4747
func findAll(batchLimit: Int?,
4848
options: API.Options) async
4949

@@ -53,7 +53,7 @@ public protocol QueryObservable: ObservableObject {
5353
- warning: This method mutates the query. It will reset the limit to `1`.
5454
- parameter options: A set of header options sent to the server. Defaults to an empty set.
5555
*/
56-
@MainActor
56+
@MainActor
5757
func first(options: API.Options) async
5858

5959
/**
@@ -62,7 +62,7 @@ public protocol QueryObservable: ObservableObject {
6262

6363
- parameter options: A set of header options sent to the server. Defaults to an empty set.
6464
*/
65-
@MainActor
65+
@MainActor
6666
func count(options: API.Options) async
6767

6868
/**
@@ -74,7 +74,7 @@ public protocol QueryObservable: ObservableObject {
7474
- parameter options: A set of header options sent to the server. Defaults to an empty set.
7575
- warning: This has not been tested thoroughly.
7676
*/
77-
@MainActor
77+
@MainActor
7878
func aggregate(_ pipeline: [[String: Encodable & Sendable]],
7979
options: API.Options) async
8080
}

Sources/ParseSwift/Types/CloudViewModel.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ open class CloudViewModel<T: ParseCloudable>: CloudObservable, @unchecked Sendab
8787
self._cloudCode = cloudCode
8888
}
8989

90-
@MainActor
9190
public func runFunction(options: API.Options = []) async {
9291
do {
9392
self.results = try await cloudCode.runFunction(options: options)
@@ -96,7 +95,6 @@ open class CloudViewModel<T: ParseCloudable>: CloudObservable, @unchecked Sendab
9695
}
9796
}
9897

99-
@MainActor
10098
public func startJob(options: API.Options = []) async {
10199
do {
102100
self.results = try await cloudCode.startJob(options: options)
@@ -107,14 +105,16 @@ open class CloudViewModel<T: ParseCloudable>: CloudObservable, @unchecked Sendab
107105
}
108106

109107
// MARK: CloudCodeViewModel
108+
@MainActor
110109
public extension ParseCloudable {
111110

112111
/**
113112
Creates a view model for this CloudCode. Suitable for `ObjectObserved`
114113
as the view model can be used as a SwiftUI publisher. Meaning it can serve
115114
indepedently as a ViewModel in MVVM.
116115
*/
117-
var viewModel: CloudViewModel<Self> {
116+
117+
var viewModel: CloudViewModel<Self> {
118118
CloudViewModel(cloudCode: self)
119119
}
120120

@@ -125,7 +125,7 @@ public extension ParseCloudable {
125125
- parameter query: Any query.
126126
- returns: The view model for this query.
127127
*/
128-
static func viewModel(_ cloudCode: Self) -> CloudViewModel<Self> {
128+
static func viewModel(_ cloudCode: Self) -> CloudViewModel<Self> {
129129
CloudViewModel(cloudCode: cloudCode)
130130
}
131131
}

0 commit comments

Comments
 (0)