diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd97794b4..e1dbdafca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,15 @@
# Parse-Swift Changelog
### main
-[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.1.0...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift)
+[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.1.1...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift)
* _Contributing to this repo? Add info about your change here to be included in the next release_
+### 5.1.1
+[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)
+
+__Fixes__
+* Instead of a possible inifinite loop, throw an error if the Swift SDK is used, but not initialized within 5 seconds of first use ([#73](https://github.com/netreconlab/Parse-Swift/pull/73)), thanks to [Corey Baker](https://github.com/cbaker6).
+
### 5.1.0
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.0.1...5.1.0), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.1.0/documentation/parseswift)
diff --git a/README.md b/README.md
index 7d43e9839..688b958e9 100644
--- a/README.md
+++ b/README.md
@@ -24,18 +24,18 @@
---
-A pure Swift library that gives you access to the powerful Parse Server backend from your Swift applications.
+A pure Swift library that gives you access to the powerful Parse Server backend. See why Parse-SwiftOG is better than all of the other Parse SDK's by looking at the [feature comparison tables](https://github.com/netreconlab/Parse-Swift/discussions/72).
-For more information about the Parse Platform and its features, see the public [documentation][docs]. The ParseSwift SDK is not a port of the [Parse-SDK-iOS-OSX SDK](https://github.com/parse-community/Parse-SDK-iOS-OSX) and though some of it may feel familiar, it is not backwards compatible and is designed using [protocol oriented programming (POP) and value types](https://www.pluralsight.com/guides/protocol-oriented-programming-in-swift) instead of OOP and reference types. You can learn more about POP by watching [Protocol-Oriented Programming in Swift](https://developer.apple.com/videos/play/wwdc2015/408/) or [Protocol and Value Oriented Programming in UIKit Apps](https://developer.apple.com/videos/play/wwdc2016/419/) videos from previous WWDC's. For more details about ParseSwift, visit the [api documentation](https://netreconlab.github.io/Parse-Swift/release/documentation/parseswift/).
+The ParseSwiftOG SDK is not a port of the [Parse-SDK-iOS-OSX SDK](https://github.com/parse-community/Parse-SDK-iOS-OSX) and though some of it may feel familiar, it is not backwards compatible and is designed using [protocol oriented programming (POP) and value types](https://www.pluralsight.com/guides/protocol-oriented-programming-in-swift) instead of OOP and reference types. You can learn more about POP by watching [Protocol-Oriented Programming in Swift](https://developer.apple.com/videos/play/wwdc2015/408/) or [Protocol and Value Oriented Programming in UIKit Apps](https://developer.apple.com/videos/play/wwdc2016/419/) videos from previous WWDC's. For more details about ParseSwift, visit the [api documentation](https://netreconlab.github.io/Parse-Swift/release/documentation/parseswift/).
To learn how to use or experiment with ParseSwift, you can run and edit the [ParseSwift.playground](https://github.com/netreconlab/Parse-Swift/tree/main/ParseSwift.playground/Pages). You can use the parse-server in [this repo](https://github.com/netreconlab/parse-hipaa/tree/parse-swift) which has docker compose files (`docker-compose up` gives you a working server) configured to connect with the playground files, has [Parse Dashboard](https://github.com/parse-community/parse-dashboard), and can be used with MongoDB or PostgreSQL. You can also configure the Swift Playgrounds to work with your own Parse Server by editing the configuation in [Common.swift](https://github.com/netreconlab/Parse-Swift/blob/e9ba846c399257100b285d25d2bd055628b13b4b/ParseSwift.playground/Sources/Common.swift#L4-L19). To learn more, check out [CONTRIBUTING.md](https://github.com/netreconlab/Parse-Swift/blob/main/CONTRIBUTING.md#swift-playgrounds).
-## Why use Parse-SwiftOG from NetReconLab?
-This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2 of the original developers of Parse-Swift](https://github.com/parse-community/Parse-Swift/graphs/contributors). Corey was responsible for the direction and development of all releases from [1.0.0](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2) to [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). This repo will remain aligned with the original core principals of a swifty framework that contains zero dependencies and takes advantage of all of the features the [parse-server](https://github.com/parse-community/parse-server) has to offer. The reason for the bifurcation from the parse-community version of Parse-Swift is due to interference and a number of disagreements with the [Parse Project Management Committee (PMC)](https://github.com/parse-community/Governance/blob/main/TEAM.md#project-management-committee-pmc) about the future of ParseSwift along with the PMC member ignoring code reviews and comments, marking relevant comments as `off-topic`, merging commits directly to main branch, lack-of-understanding of Swift fundamentals, client-side development, and lack of knowledge about how the Parse-Swift framework is designed. It is important to emphasize that no member of the Parse PMC participated in the design, release, and direction of Parse-Swift from its [first commit](https://github.com/parse-community/Parse-Swift/tree/cf69b7f0638819a7070b82228bc51a97df5757e6) to version [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). The Parse PMC also does not properly give attribution or show appreciation to Corey as the lead developer of Parse-Swift. In addition, no funding or support from the [parse-community funds](https://opencollective.com/parse-server) has ever been offered to Corey for any of his [contributions to Parse](https://github.com/search?q=user%3Aparse-community+author%3Acbaker6&type=issues), though a [number of funds have been offered and made to other contributors](https://opencollective.com/parse-server#category-BUDGET). If you benefit from Parse-Swift and would like to show monetary support, feel free to
+## Why use Parse-SwiftOG from NetReconLab?
+This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2 of the original developers of Parse-Swift](https://github.com/parse-community/Parse-Swift/graphs/contributors). Corey was responsible for the direction and development of all releases from [1.0.0](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2) to [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). This repo will remain aligned with the original core principals of a swifty framework that contains zero dependencies and takes advantage of all of the features the [parse-server](https://github.com/parse-community/parse-server) has to offer. You can learn more about why I left the parse-community and created my own repo [here](https://github.com/netreconlab/Parse-Swift/discussions/7). If you benefit from Parse-Swift and would like to show monetary support, feel free to
---
-- [ Why use Parse-SwiftOG from NetReconLab?](#-why-use-parse-swift-from-netreconlab)
+- [Why use Parse-SwiftOG from NetReconLab?](https://github.com/netreconlab/Parse-Swift/discussions/7)
- [Installation](#installation)
- [Swift Package Manager](#swift-package-manager)
- [CocoaPods](#cocoapods)
@@ -48,7 +48,7 @@ This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2
- [SwiftUI View Models Using Combine](#swiftui-view-models-using-combine)
- [Traditional Callbacks](#traditional-callbacks)
- [Advanced Usage](#advanced-usage)
-- [Migration from Parse ObjC SDK](#migration-from-parse-objc-sdk)
+- [Migrating from Older Versions and SDKs](#migrating-from-older-versions-and-sdks)
## Installation
@@ -57,13 +57,13 @@ This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2
You can use The Swift Package Manager (SPM) to install ParseSwift by adding the following description to your `Package.swift` file:
```swift
-// swift-tools-version:5.5
+// swift-tools-version:5.5.2
import PackageDescription
let package = Package(
name: "YOUR_PROJECT_NAME",
dependencies: [
- .package(url: "https://github.com/netreconlab/Parse-Swift", .upToNextMajor(from: "5.1.0")),
+ .package(url: "https://github.com/netreconlab/Parse-Swift", .upToNextMajor(from: "5.1.1")),
]
)
```
@@ -102,9 +102,9 @@ Below is a list of apps and frameworks that use Parse-Swift to help developers t
After installing ParseSwift, to use it first `import ParseSwift` in your AppDelegate.swift and then add the following code in your `application:didFinishLaunchingWithOptions:` method:
```swift
-ParseSwift.initialize(applicationId: "xxxxxxxxxx", clientKey: "xxxxxxxxxx", serverURL: URL(string: "https://example.com")!)
+try await ParseSwift.initialize(applicationId: "xxxxxxxxxx", clientKey: "xxxxxxxxxx", serverURL: URL(string: "https://example.com")!)
```
-Please checkout the [Swift Playground](https://github.com/netreconlab/Parse-Swift/tree/main/ParseSwift.playground) for more usage information.
+Please checkout the [Swift Playground](https://github.com/netreconlab/Parse-Swift/tree/main/ParseSwift.playground/Pages) for more usage information.
## LiveQuery
@@ -254,18 +254,11 @@ Handling errors is and other events is similar, take a look at the `Subscription
You are not limited to a single Live Query Client - you can create multiple instances of `ParseLiveQuery`, use certificate authentication and pinning, receive metrics about each client connection, connect to individual server URLs, and more.
-[docs]: https://docs.parseplatform.org
[license-link]: LICENSE
-## Migration from Parse ObjC SDK
+## Migrating from Older Versions and SDKs
-An example application that demonstrates how to convert from the Objective-C SDK to the Swift SDK can be found at [ParseMigrateKeychain](https://github.com/netreconlab/ParseMigrateKeychain). Specifically, look at [ContentViewModel.swift](https://github.com/netreconlab/ParseMigrateKeychain/blob/main/ParseMigrateKeychain/ContentViewModel.swift) for code examples. The basic steps are below:
+1. See the [discussion](https://github.com/netreconlab/Parse-Swift/discussions/74) to learn how to migrate from Parse-SwiftOG 4.15.0+ to 5.1.1+
+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)
+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)
-1. Add the ParseSwift framework to your project using SPM
-1. Remove the Objective-C SDK from your project
-1. Add the Objective-C SDK to your project using SPM. You can use this [repo](https://github.com/mman/Parse-SDK-iOS-OSX/tree/spm) as you will only need `PFUser` and `PFInstallation`
-1. Convert your app from using the Objective-C SDK to the Swift SDK
-1. Create a `User` and `Installation` [model](https://github.com/netreconlab/ParseMigrateKeychain/tree/main/ParseMigrateKeychain/Models) that conform to `ParseObject`
-1. Migrate `PFUser->User` using [loginusingobjckeychain](https://swiftpackageindex.com/parse-community/parse-swift/4.14.2/documentation/parseswift/parseuser/loginusingobjckeychain(options:))
-1. Migrate `PFInstallation->Installation` using [become](https://swiftpackageindex.com/parse-community/parse-swift/4.14.2/documentation/parseswift/parseinstallation/become(_:copyentireinstallation:options:))
-1. Remove the Objective-C SDK from your project after you have migrated all users
diff --git a/Sources/ParseSwift/API/API+Command.swift b/Sources/ParseSwift/API/API+Command.swift
index 63e2c22d8..11498fab5 100644
--- a/Sources/ParseSwift/API/API+Command.swift
+++ b/Sources/ParseSwift/API/API+Command.swift
@@ -256,59 +256,70 @@ internal extension API {
completion: @escaping(Result) -> Void) {
let params = self.params?.getURLQueryItems()
Task {
- var headers = await API.getHeaders(options: options)
- if method == .GET || method == .DELETE {
- headers.removeValue(forKey: "X-Parse-Request-Id")
- }
- let url = parseURL == nil ?
- API.serverURL(options: options).appendingPathComponent(path.urlComponent) : parseURL!
+ do {
+ var headers = try await API.getHeaders(options: options)
+ if method == .GET || method == .DELETE {
+ headers.removeValue(forKey: "X-Parse-Request-Id")
+ }
+ let url = parseURL == nil ?
+ API.serverURL(options: options).appendingPathComponent(path.urlComponent) : parseURL!
- guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
- let error = ParseError(code: .otherCause,
- message: "Could not unrwrap url components for \(url)")
- completion(.failure(error))
- return
- }
- components.queryItems = params
+ guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
+ let error = ParseError(code: .otherCause,
+ message: "Could not unrwrap url components for \(url)")
+ completion(.failure(error))
+ return
+ }
+ components.queryItems = params
- guard let urlComponents = components.url else {
- let error = ParseError(code: .otherCause,
- message: "Could not create url from components for \(components)")
- completion(.failure(error))
- return
- }
+ guard let urlComponents = components.url else {
+ let error = ParseError(code: .otherCause,
+ message: "Could not create url from components for \(components)")
+ completion(.failure(error))
+ return
+ }
- var urlRequest = URLRequest(url: urlComponents)
- urlRequest.allHTTPHeaderFields = headers
- if let urlBody = body {
- if (urlBody as? ParseCloudTypeable) != nil {
- guard let bodyData = try? ParseCoding.parseEncoder().encode(urlBody, skipKeys: .cloud) else {
- let error = ParseError(code: .otherCause,
- message: "Could not encode body \(urlBody)")
- completion(.failure(error))
- return
- }
- urlRequest.httpBody = bodyData
- } else {
- guard let bodyData = try? ParseCoding
- .parseEncoder()
- .encode(urlBody,
- batching: batching,
- collectChildren: false,
- objectsSavedBeforeThisOne: childObjects,
- filesSavedBeforeThisOne: childFiles) else {
- let error = ParseError(code: .otherCause,
- message: "Could not encode body \(urlBody)")
- completion(.failure(error))
- return
+ var urlRequest = URLRequest(url: urlComponents)
+ urlRequest.allHTTPHeaderFields = headers
+ if let urlBody = body {
+ if (urlBody as? ParseCloudTypeable) != nil {
+ do {
+ let bodyData = try ParseCoding.parseEncoder().encode(urlBody,
+ skipKeys: .cloud)
+ urlRequest.httpBody = bodyData
+ } catch {
+ let defaultError = ParseError(code: .otherCause,
+ message: "Could not encode body \(urlBody)",
+ swift: error)
+ let parseError = error as? ParseError ?? defaultError
+ completion(.failure(parseError))
+ return
+ }
+ } else {
+ guard let bodyData = try? ParseCoding
+ .parseEncoder()
+ .encode(urlBody,
+ batching: batching,
+ collectChildren: false,
+ objectsSavedBeforeThisOne: childObjects,
+ filesSavedBeforeThisOne: childFiles) else {
+ let error = ParseError(code: .otherCause,
+ message: "Could not encode body \(urlBody)")
+ completion(.failure(error))
+ return
+ }
+ urlRequest.httpBody = bodyData.encoded
}
- urlRequest.httpBody = bodyData.encoded
}
+ urlRequest.httpMethod = method.rawValue
+ urlRequest.cachePolicy = requestCachePolicy(options: options)
+ completion(.success(urlRequest))
+ return
+ } catch {
+ let parseError = error as? ParseError ?? ParseError(swift: error)
+ completion(.failure(parseError))
+ return
}
- urlRequest.httpMethod = method.rawValue
- urlRequest.cachePolicy = requestCachePolicy(options: options)
- completion(.success(urlRequest))
- return
}
}
@@ -527,23 +538,19 @@ internal extension API.Command where T: ParseObject {
method: object.element.method)
return .success(updatedObject)
} catch {
- guard let parseError = error as? ParseError else {
- return .failure(ParseError(swift: error))
- }
+ let parseError = error as? ParseError ?? ParseError(swift: error)
return .failure(parseError)
}
} else {
- guard let parseError = response.error else {
- return .failure(ParseError(code: .otherCause, message: "unknown error"))
- }
-
+ let parseError = response.error ?? ParseError(code: .otherCause,
+ message: "Unknown error")
return .failure(parseError)
}
})
} catch {
- guard let parseError = error as? ParseError else {
- return [(.failure(ParseError(code: .otherCause, message: "decoding error: \(error)")))]
- }
+ let parseError = error as? ParseError ?? ParseError(code: .otherCause,
+ message: "Decoding error",
+ swift: error)
return [(.failure(parseError))]
}
}
@@ -572,17 +579,15 @@ internal extension API.Command where T: ParseObject {
if response.success != nil {
return .success(())
} else {
- guard let parseError = response.error else {
- return .failure(ParseError(code: .otherCause, message: "unknown error"))
- }
-
+ let parseError = response.error ?? ParseError(code: .otherCause,
+ message: "Unknown error")
return .failure(parseError)
}
})
} catch {
- guard let parseError = error as? ParseError else {
- return [(.failure(ParseError(code: .otherCause, message: "decoding error: \(error)")))]
- }
+ let parseError = error as? ParseError ?? ParseError(code: .otherCause,
+ message: "Decoding error",
+ swift: error)
return [(.failure(parseError))]
}
}
diff --git a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift
index 04705e71a..c1a77421e 100644
--- a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift
+++ b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift
@@ -90,35 +90,43 @@ internal extension API {
// MARK: URL Preperation
func prepareURLRequest(options: API.Options) async -> Result {
let params = self.params?.getURLQueryItems()
- var headers = await API.getHeaders(options: options)
- if method == .GET || method == .DELETE {
- headers.removeValue(forKey: "X-Parse-Request-Id")
- }
- let url = API.serverURL(options: options).appendingPathComponent(path.urlComponent)
-
- guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
- return .failure(ParseError(code: .otherCause,
- message: "Could not unrwrap url components for \(url)"))
- }
- components.queryItems = params
+ do {
+ var headers = try await API.getHeaders(options: options)
+ if method == .GET || method == .DELETE {
+ headers.removeValue(forKey: "X-Parse-Request-Id")
+ }
+ let url = API.serverURL(options: options).appendingPathComponent(path.urlComponent)
- guard let urlComponents = components.url else {
- return .failure(ParseError(code: .otherCause,
- message: "Could not create url from components for \(components)"))
- }
+ guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
+ return .failure(ParseError(code: .otherCause,
+ message: "Could not unrwrap url components for \(url)"))
+ }
+ components.queryItems = params
- var urlRequest = URLRequest(url: urlComponents)
- urlRequest.allHTTPHeaderFields = headers
- if let urlBody = body {
- guard let bodyData = try? ParseCoding.jsonEncoder().encode(urlBody) else {
+ guard let urlComponents = components.url else {
return .failure(ParseError(code: .otherCause,
- message: "Could not encode body \(urlBody)"))
+ message: "Could not create url from components for \(components)"))
+ }
+
+ var urlRequest = URLRequest(url: urlComponents)
+ urlRequest.allHTTPHeaderFields = headers
+ if let urlBody = body {
+ do {
+ let bodyData = try ParseCoding.jsonEncoder().encode(urlBody)
+ urlRequest.httpBody = bodyData
+ } catch {
+ return .failure(ParseError(code: .otherCause,
+ message: "Could not encode body \(urlBody)",
+ swift: error))
+ }
}
- urlRequest.httpBody = bodyData
+ urlRequest.httpMethod = method.rawValue
+ urlRequest.cachePolicy = requestCachePolicy(options: options)
+ return .success(urlRequest)
+ } catch {
+ let parseError = error as? ParseError ?? ParseError(swift: error)
+ return .failure(parseError)
}
- urlRequest.httpMethod = method.rawValue
- urlRequest.cachePolicy = requestCachePolicy(options: options)
- return .success(urlRequest)
}
enum CodingKeys: String, CodingKey { // swiftlint:disable:this nesting
@@ -199,20 +207,19 @@ internal extension API.NonParseBodyCommand {
let response = responses[object.offset]
if let success = response.success {
guard let successfulResponse = try? object.element.mapper(success) else {
- return.failure(ParseError(code: .otherCause, message: "unknown error"))
+ return .failure(ParseError(code: .otherCause, message: "Unknown error"))
}
return .success(successfulResponse)
} else {
- guard let parseError = response.error else {
- return .failure(ParseError(code: .otherCause, message: "unknown error"))
- }
+ let parseError = response.error ?? ParseError(code: .otherCause,
+ message: "Unknown error")
return .failure(parseError)
}
})
} catch {
- guard let parseError = error as? ParseError else {
- return [(.failure(ParseError(code: .otherCause, message: "decoding error: \(error)")))]
- }
+ let parseError = error as? ParseError ?? ParseError(code: .otherCause,
+ message: "Decoding error",
+ swift: error)
return [(.failure(parseError))]
}
}
diff --git a/Sources/ParseSwift/API/API.swift b/Sources/ParseSwift/API/API.swift
index 295413188..47451b9a6 100644
--- a/Sources/ParseSwift/API/API.swift
+++ b/Sources/ParseSwift/API/API.swift
@@ -203,7 +203,8 @@ public struct API {
}
// swiftlint:disable:next cyclomatic_complexity
- internal static func getHeaders(options: API.Options) async -> [String: String] {
+ internal static func getHeaders(options: API.Options) async throws -> [String: String] {
+ try await yieldIfNotInitialized()
var headers: [String: String] = ["X-Parse-Application-Id": Parse.configuration.applicationId,
"Content-Type": "application/json"]
if let clientKey = Parse.configuration.clientKey {
@@ -253,7 +254,6 @@ public struct API {
break
}
}
-
return headers
}
diff --git a/Sources/ParseSwift/Documentation.docc/ParseSwift.md b/Sources/ParseSwift/Documentation.docc/ParseSwift.md
index a8b207ff2..d4145be3f 100644
--- a/Sources/ParseSwift/Documentation.docc/ParseSwift.md
+++ b/Sources/ParseSwift/Documentation.docc/ParseSwift.md
@@ -9,7 +9,7 @@ For more information about the Parse Platform and its features, see the public [
To learn how to use or experiment with ParseSwift, you can run and edit the [ParseSwift.playground](https://github.com/netreconlab/Parse-Swift/tree/main/ParseSwift.playground/Pages). You can use the parse-server in [this repo](https://github.com/netreconlab/parse-hipaa/tree/parse-swift) which has docker compose files (`docker-compose up` gives you a working server) configured to connect with the playground files, has [Parse Dashboard](https://github.com/parse-community/parse-dashboard), and can be used with MongoDB or PostgreSQL. You can also configure the Swift Playgrounds to work with your own Parse Server by editing the configuation in [Common.swift](https://github.com/netreconlab/Parse-Swift/blob/e9ba846c399257100b285d25d2bd055628b13b4b/ParseSwift.playground/Sources/Common.swift#L4-L19). To learn more, check out [CONTRIBUTING.md](https://github.com/netreconlab/Parse-Swift/blob/main/CONTRIBUTING.md#swift-playgrounds).
## Why use Parse-Swift from NetReconLab?
-This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2 of the original developers of Parse-Swift](https://github.com/parse-community/Parse-Swift/graphs/contributors). Corey was responsible for the direction and development of all releases from [1.0.0](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2) to [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). This repo will remain aligned with the original core principals of a swifty framework that contains zero dependencies and takes advantage of all of the features the [parse-server](https://github.com/parse-community/parse-server) has to offer. The reason for the bifurcation from the parse-community version of Parse-Swift is due to interference and a number of disagreements with the [Parse Project Management Committee (PMC)](https://github.com/parse-community/Governance/blob/main/TEAM.md#project-management-committee-pmc) about the future of ParseSwift along with the PMC member ignoring code reviews and comments, marking relevant comments as `off-topic`, merging commits directly to main branch, lack-of-understanding of Swift fundamentals, client-side development, and lack of knowledge about how the Parse-Swift framework is designed. It is important to emphasize that no member of the Parse PMC participated in the design, release, and direction of Parse-Swift from its [first commit](https://github.com/parse-community/Parse-Swift/tree/cf69b7f0638819a7070b82228bc51a97df5757e6) to version [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). The Parse PMC also does not properly give attribution or show appreciation to Corey as the lead developer of Parse-Swift. In addition, no funding or support from the [parse-community funds](https://opencollective.com/parse-server) has ever been offered to Corey for any of his [contributions to Parse](https://github.com/search?q=user%3Aparse-community+author%3Acbaker6&type=issues), though a [number of funds have been offered and made to other contributors](https://opencollective.com/parse-server#category-BUDGET).
+This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2 of the original developers of Parse-Swift](https://github.com/parse-community/Parse-Swift/graphs/contributors). Corey was responsible for the direction and development of all releases from [1.0.0](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2) to [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). This repo will remain aligned with the original core principals of a swifty framework that contains zero dependencies and takes advantage of all of the features the [parse-server](https://github.com/parse-community/parse-server) has to offer. You can learn more about why I left the parse-community and created my own repo [here](https://github.com/netreconlab/Parse-Swift/discussions/7).
## Topics
diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift
index 1dda4bba0..8a78b384e 100644
--- a/Sources/ParseSwift/Extensions/URLSession.swift
+++ b/Sources/ParseSwift/Extensions/URLSession.swift
@@ -42,17 +42,13 @@ internal extension URLSession {
responseError: Error?,
mapper: @escaping (Data) async throws -> U) async -> Result {
if let responseError = responseError {
- guard let parseError = responseError as? ParseError else {
- return .failure(ParseError(message: "Unable to connect with parse-server",
- swift: responseError))
- }
+ let parseError = responseError as? ParseError ?? ParseError(message: "Unable to connect with parse-server",
+ swift: responseError)
return .failure(parseError)
}
guard let response = urlResponse else {
- guard let parseError = responseError as? ParseError else {
- return .failure(ParseError(code: .otherCause,
- message: "No response from server"))
- }
+ let parseError = responseError as? ParseError ?? ParseError(code: .otherCause,
+ message: "No response from server")
return .failure(parseError)
}
if var responseData = responseData {
@@ -112,17 +108,14 @@ internal extension URLSession {
responseError: Error?,
mapper: @escaping (Data) async throws -> U) async -> Result {
guard let response = urlResponse else {
- guard let parseError = responseError as? ParseError else {
- return .failure(ParseError(code: .otherCause,
- message: "No response from server"))
- }
+ let parseError = responseError as? ParseError ?? ParseError(code: .otherCause,
+ message: "No response from server")
return .failure(parseError)
}
if let responseError = responseError {
- guard let parseError = responseError as? ParseError else {
- return .failure(ParseError(message: "Unable to connect with parse-server",
- swift: responseError))
- }
+ let defaultError = ParseError(message: "Unable to connect with parse-server",
+ swift: responseError)
+ let parseError = responseError as? ParseError ?? defaultError
return .failure(parseError)
}
diff --git a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift
index 84c353914..2ad7cb034 100644
--- a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift
+++ b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift
@@ -193,7 +193,7 @@ Not attempting to open ParseLiveQuery socket anymore
return
}
isConfiguring = true
- await yieldIfNotInitialized()
+ try await yieldIfNotInitialized()
Self.defaultClient = try await Self(isDefault: true)
}
diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift
index 4bb9e2a7d..ae04e8953 100644
--- a/Sources/ParseSwift/Objects/ParseInstallation.swift
+++ b/Sources/ParseSwift/Objects/ParseInstallation.swift
@@ -286,7 +286,7 @@ public extension ParseInstallation {
- throws: An error of `ParseError` type.
*/
static func current() async throws -> Self {
- await yieldIfNotInitialized()
+ try await yieldIfNotInitialized()
guard let installation = await Self.currentContainer().currentInstallation else {
throw ParseError(code: .otherCause,
message: "There is no current Installation")
diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift
index f0a1c585c..b191f28f2 100644
--- a/Sources/ParseSwift/Objects/ParseUser.swift
+++ b/Sources/ParseSwift/Objects/ParseUser.swift
@@ -163,7 +163,7 @@ public extension ParseUser {
- throws: An error of `ParseError` type.
*/
static func current() async throws -> Self {
- await yieldIfNotInitialized()
+ try await yieldIfNotInitialized()
guard let container = await Self.currentContainer(),
let user = container.currentUser else {
throw ParseError(code: .otherCause,
diff --git a/Sources/ParseSwift/Parse.swift b/Sources/ParseSwift/Parse.swift
index 69f1c3c66..bb43d2395 100644
--- a/Sources/ParseSwift/Parse.swift
+++ b/Sources/ParseSwift/Parse.swift
@@ -61,10 +61,15 @@ internal func initialize(applicationId: String,
try await initialize(configuration: configuration)
}
-internal func yieldIfNotInitialized() async {
+internal func yieldIfNotInitialized(_ iteration: Int = 0) async throws {
guard ParseConfiguration.checkIfConfigured() else {
- await Task.yield()
- await yieldIfNotInitialized()
+ guard iteration < 5 else {
+ throw ParseError(code: .otherCause,
+ message: "The SDK needs to be initialized")
+ }
+ let nanoSeconds = UInt64(1 * 1_000_000_000)
+ try await Task.sleep(nanoseconds: nanoSeconds)
+ try await yieldIfNotInitialized(iteration + 1)
return
}
}
@@ -158,21 +163,7 @@ public func initialize(configuration: ParseConfiguration) async throws { // swif
try await ParseVersion.setCurrent(try ParseVersion(string: ParseConstants.version))
}
- // Migrate installations with installationId, but missing
- // currentInstallation, ParseSwift < 1.9.10
let currentInstallationContainer = await BaseParseInstallation.currentContainer()
- if let installationId = currentInstallationContainer.installationId,
- currentInstallationContainer.currentInstallation == nil {
- if let foundInstallation = try? await BaseParseInstallation
- .query("installationId" == installationId)
- .first(options: [.cachePolicy(.reloadIgnoringLocalCacheData)]) {
- let newContainer = CurrentInstallationContainer(currentInstallation: foundInstallation,
- installationId: installationId)
- await BaseParseInstallation.setCurrentContainer(newContainer)
- }
- }
- await BaseParseInstallation.createNewInstallationIfNeeded()
-
#if !os(Linux) && !os(Android) && !os(Windows)
if configuration.isMigratingFromObjcSDK {
await KeychainStore.createObjectiveC()
@@ -189,7 +180,24 @@ public func initialize(configuration: ParseConfiguration) async throws { // swif
}
}
#endif
- Parse.configuration.isInitialized = true
+
+ // Migrate installations with installationId, but missing
+ // currentInstallation, ParseSwift < 1.9.10
+ if let installationId = currentInstallationContainer.installationId,
+ currentInstallationContainer.currentInstallation == nil {
+ Parse.configuration.isInitialized = true
+ if let foundInstallation = try? await BaseParseInstallation
+ .query("installationId" == installationId)
+ .first(options: [.cachePolicy(.reloadIgnoringLocalCacheData)]) {
+ let newContainer = CurrentInstallationContainer(currentInstallation: foundInstallation,
+ installationId: installationId)
+ await BaseParseInstallation.setCurrentContainer(newContainer)
+ }
+ }
+ await BaseParseInstallation.createNewInstallationIfNeeded()
+ if !Parse.configuration.isInitialized {
+ Parse.configuration.isInitialized = true
+ }
}
/**
@@ -347,7 +355,7 @@ public func deleteObjectiveCKeychain() async throws {
throw ParseError(code: .otherCause,
message: "\"accessGroup\" must be set to a valid string when \"synchronizeAcrossDevices == true\"")
}
- await yieldIfNotInitialized()
+ try await yieldIfNotInitialized()
guard let currentAccessGroup = try? await ParseKeychainAccessGroup.current() else {
throw ParseError(code: .otherCause,
message: "Problem unwrapping the current access group. Did you initialize the SDK before calling this method?")
diff --git a/Sources/ParseSwift/ParseConstants.swift b/Sources/ParseSwift/ParseConstants.swift
index 3b1bbcbf3..551ba079b 100644
--- a/Sources/ParseSwift/ParseConstants.swift
+++ b/Sources/ParseSwift/ParseConstants.swift
@@ -10,7 +10,7 @@ import Foundation
enum ParseConstants {
static let sdk = "swift"
- static let version = "5.1.0"
+ static let version = "5.1.1"
static let fileManagementDirectory = "parse/"
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
static let fileManagementLibraryDirectory = "Library/"
diff --git a/Sources/ParseSwift/Protocols/ParseConfig.swift b/Sources/ParseSwift/Protocols/ParseConfig.swift
index 3aaf89a46..82a39920e 100644
--- a/Sources/ParseSwift/Protocols/ParseConfig.swift
+++ b/Sources/ParseSwift/Protocols/ParseConfig.swift
@@ -103,10 +103,25 @@ struct CurrentConfigContainer: Codable, Equatable {
var currentConfig: T?
}
-public extension ParseConfig {
+extension ParseConfig {
+
+ /**
+ Gets/Sets properties of the current config in the Keychain.
+
+ - returns: Returns the latest `ParseConfig` on this device. If there is none, throws an error.
+ - throws: An error of `ParseError` type.
+ */
+ public static func current() async throws -> Self {
+ try await yieldIfNotInitialized()
+ guard let container = await Self.currentContainer(),
+ let config = container.currentConfig else {
+ throw ParseError(code: .otherCause,
+ message: "There is no current Config")
+ }
+ return config
+ }
- internal static func currentContainer() async -> CurrentConfigContainer? {
- await yieldIfNotInitialized()
+ static func currentContainer() async -> CurrentConfigContainer? {
guard let configInMemory: CurrentConfigContainer =
try? await ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentConfig) else {
#if !os(Linux) && !os(Android) && !os(Windows)
@@ -118,14 +133,14 @@ public extension ParseConfig {
return configInMemory
}
- internal static func setCurrentContainer(_ newValue: CurrentConfigContainer?) async {
+ static func setCurrentContainer(_ newValue: CurrentConfigContainer?) async {
try? await ParseStorage.shared.set(newValue, for: ParseStorage.Keys.currentConfig)
#if !os(Linux) && !os(Android) && !os(Windows)
try? await KeychainStore.shared.set(newValue, for: ParseStorage.Keys.currentConfig)
#endif
}
- internal static func updateStorageIfNeeded(_ result: Self, deleting: Bool = false) async {
+ static func updateStorageIfNeeded(_ result: Self, deleting: Bool = false) async {
if !deleting {
await Self.setCurrent(result)
} else {
@@ -133,29 +148,14 @@ public extension ParseConfig {
}
}
- internal static func deleteCurrentContainerFromStorage() async {
+ static func deleteCurrentContainerFromStorage() async {
try? await ParseStorage.shared.delete(valueFor: ParseStorage.Keys.currentConfig)
#if !os(Linux) && !os(Android) && !os(Windows)
try? await KeychainStore.shared.delete(valueFor: ParseStorage.Keys.currentConfig)
#endif
}
- /**
- Gets/Sets properties of the current config in the Keychain.
-
- - returns: Returns the latest `ParseConfig` on this device. If there is none, throws an error.
- - throws: An error of `ParseError` type.
- */
- static func current() async throws -> Self {
- guard let container = await Self.currentContainer(),
- let config = container.currentConfig else {
- throw ParseError(code: .otherCause,
- message: "There is no current Config")
- }
- return config
- }
-
- internal static func setCurrent(_ current: Self?) async {
+ static func setCurrent(_ current: Self?) async {
if await Self.currentContainer() == nil {
await Self.setCurrentContainer(CurrentConfigContainer())
}
diff --git a/Sources/ParseSwift/Types/ParseConfigCodable.swift b/Sources/ParseSwift/Types/ParseConfigCodable.swift
index b97cbca3a..b521f7a2c 100644
--- a/Sources/ParseSwift/Types/ParseConfigCodable.swift
+++ b/Sources/ParseSwift/Types/ParseConfigCodable.swift
@@ -110,6 +110,7 @@ extension ParseConfigCodable {
- throws: An error of `ParseError` type.
*/
public static func current() async throws -> [String: V] {
+ try await yieldIfNotInitialized()
guard let container = await Self.currentContainer(),
let config = container.currentConfig else {
throw ParseError(code: .otherCause,
@@ -119,7 +120,6 @@ extension ParseConfigCodable {
}
static func currentContainer() async -> CurrentConfigDictionaryContainer? {
- await yieldIfNotInitialized()
guard let configInMemory: CurrentConfigDictionaryContainer =
try? await ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentConfig) else {
#if !os(Linux) && !os(Android) && !os(Windows)
diff --git a/Tests/ParseSwiftTests/APICommandTests.swift b/Tests/ParseSwiftTests/APICommandTests.swift
index 2eaa126c0..063361211 100644
--- a/Tests/ParseSwiftTests/APICommandTests.swift
+++ b/Tests/ParseSwiftTests/APICommandTests.swift
@@ -336,8 +336,8 @@ class APICommandTests: XCTestCase {
}
}
- func testApplicationIdHeader() async {
- let headers = await API.getHeaders(options: [])
+ func testApplicationIdHeader() async throws {
+ let headers = try await API.getHeaders(options: [])
XCTAssertEqual(headers["X-Parse-Application-Id"], ParseSwift.configuration.applicationId)
let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in
@@ -359,7 +359,7 @@ class APICommandTests: XCTestCase {
throw ParseError(code: .otherCause, message: "Parse configuration should contain key")
}
- let headers = await API.getHeaders(options: [])
+ let headers = try await API.getHeaders(options: [])
XCTAssertEqual(headers["X-Parse-Client-Key"], clientKey)
let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in
@@ -381,7 +381,7 @@ class APICommandTests: XCTestCase {
throw ParseError(code: .otherCause, message: "Parse configuration should contain key")
}
- let headers = await API.getHeaders(options: [])
+ let headers = try await API.getHeaders(options: [])
XCTAssertNil(headers["X-Parse-Master-Key"])
let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in
@@ -406,7 +406,7 @@ class APICommandTests: XCTestCase {
throw ParseError(code: .otherCause, message: "Parse current user should have session token")
}
- let headers = await API.getHeaders(options: [])
+ let headers = try await API.getHeaders(options: [])
XCTAssertEqual(headers["X-Parse-Session-Token"], sessionToken)
let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in
@@ -444,7 +444,7 @@ class APICommandTests: XCTestCase {
throw ParseError(code: .otherCause, message: "Parse current user should have session token")
}
- let headers = await API.getHeaders(options: [])
+ let headers = try await API.getHeaders(options: [])
XCTAssertEqual(headers["X-Parse-Installation-Id"], installationId)
let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in
@@ -477,7 +477,7 @@ class APICommandTests: XCTestCase {
}
func testContentHeader() async throws {
- let headers = await API.getHeaders(options: [])
+ let headers = try await API.getHeaders(options: [])
XCTAssertEqual(headers["Content-Type"], "application/json")
let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in
@@ -495,7 +495,7 @@ class APICommandTests: XCTestCase {
}
func testReplaceContentHeader() async throws {
- let headers = await API.getHeaders(options: [])
+ let headers = try await API.getHeaders(options: [])
XCTAssertEqual(headers["Content-Type"], "application/json")
let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in
@@ -528,7 +528,7 @@ class APICommandTests: XCTestCase {
}
func testRemoveContentHeader() async throws {
- let headers = await API.getHeaders(options: [])
+ let headers = try await API.getHeaders(options: [])
XCTAssertEqual(headers["Content-Type"], "application/json")
let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in
@@ -565,7 +565,7 @@ class APICommandTests: XCTestCase {
// swiftlint:disable:next function_body_length cyclomatic_complexity
func testClientVersionHeader() async throws {
- let headers = await API.getHeaders(options: [])
+ let headers = try await API.getHeaders(options: [])
XCTAssertEqual(headers["X-Parse-Client-Version"], API.clientVersion())
let post = API.Command(method: .POST, path: .login) { _ in
@@ -636,7 +636,7 @@ class APICommandTests: XCTestCase {
// swiftlint:disable:next function_body_length cyclomatic_complexity
func testIdempodency() async throws {
- let headers = await API.getHeaders(options: [])
+ let headers = try await API.getHeaders(options: [])
XCTAssertNotNil(headers["X-Parse-Request-Id"])
let post = API.Command(method: .POST, path: .login) { _ in
@@ -707,7 +707,7 @@ class APICommandTests: XCTestCase {
// swiftlint:disable:next function_body_length cyclomatic_complexity
func testIdempodencyNoParseBody() async throws {
- let headers = await API.getHeaders(options: [])
+ let headers = try await API.getHeaders(options: [])
XCTAssertNotNil(headers["X-Parse-Request-Id"])
let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in
@@ -805,7 +805,7 @@ class APICommandTests: XCTestCase {
}
func testContextHeader() async throws {
- let headers = await API.getHeaders(options: [])
+ let headers = try await API.getHeaders(options: [])
XCTAssertNil(headers["X-Parse-Cloud-Context"])
let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in