Skip to content

Commit

Permalink
feat: retry URL connection on error (#291)
Browse files Browse the repository at this point in the history
* feat: retry URL connection on error

* fix linux and windows test

* nits

* fix acl and playgrounds

* Fix errors in test cases

* Improve
  • Loading branch information
cbaker6 authored Nov 30, 2021
1 parent ae47862 commit 4734212
Show file tree
Hide file tree
Showing 38 changed files with 534 additions and 391 deletions.
15 changes: 13 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@

### main

[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.6...main)
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.3.0...main)
* _Contributing to this repo? Add info about your change here to be included in the next release_

### 2.3.0
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.6...2.3.0)

__New features__
- Add a retry mechanism to the SDK that randomly (up to 3 seconds each) tries to reconnect up to 5 times. The developer can increase or reduce the amount of retries when configuring the SDK ([#291](https://github.com/parse-community/Parse-Swift/pull/291)), thanks to [Corey Baker](https://github.com/cbaker6).
- Add toCLLocation and toCLLocationCoordinate2D methods for easy conversion from a ParseGeoPoint object. ([#287](https://github.com/parse-community/Parse-Swift/pull/287)), thanks to [Jayson Ng](https://github.com/jaysonng).

__Fixes__
- Fixed an issue where an annonymous couldn't be turned into a regular user using signup ([#291](https://github.com/parse-community/Parse-Swift/pull/291)), thanks to [Corey Baker](https://github.com/cbaker6).
- The default ACL is now deleted from the keychain when a user is logged out. This previously caused an issue when logging out a user and logging in as a different user caused all objects to only have ACL permisions for the logged in user ([#291](https://github.com/parse-community/Parse-Swift/pull/291)), thanks to [Corey Baker](https://github.com/cbaker6).

### 2.2.6
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.5...2.2.6)

__Fixes__
- Use default ACL automatically on newley created ParseObject's if a default ACL is available ([#283](https://github.com/parse-community/Parse-Swift/pull/283)), thanks to [Corey Baker](https://github.com/cbaker6).
- Use default ACL automatically on newley created ParseObject's if a default ACL is available ([#284](https://github.com/parse-community/Parse-Swift/pull/284)), thanks to [Corey Baker](https://github.com/cbaker6).

### 2.2.5
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.4...2.2.5)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ score.save { result in
assert(savedScore.objectId != nil)
assert(savedScore.createdAt != nil)
assert(savedScore.updatedAt != nil)
assert(savedScore.ACL == nil)
assert(savedScore.score == 10)

/*: To modify, need to make it a var as the value type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ score.save { result in
assert(savedScore.objectId != nil)
assert(savedScore.createdAt != nil)
assert(savedScore.updatedAt != nil)
assert(savedScore.ACL == nil)
assert(savedScore.score == 10)

//: Now that this object has a `createdAt`, it's properly saved to the server.
Expand Down Expand Up @@ -120,7 +119,7 @@ scoreToFetch.fetch { result in
case .success(let fetchedScore):
print("Successfully fetched: \(fetchedScore)")
case .failure(let error):
assertionFailure("Error fetching: \(error)")
assertionFailure("Error fetching on purpose: \(error)")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,32 @@ initializeParse()

//: To track when the app has been opened, do the following.
ParseAnalytics.trackAppOpened { result in
if case .success = result {
switch result {
case .success:
print("Saved analytics for app opened.")
case .failure(let error):
print(error)
}
}

//: To track any event, do the following.
var friendEvent = ParseAnalytics(name: "openedFriendList")
friendEvent.track { result in
if case .success = result {
switch result {
case .success:
print("Saved analytics for custom event.")
case .failure(let error):
print(error)
}
}

//: You can also add dimensions to your analytics.
friendEvent.track(dimensions: ["more": "info"]) { result in
if case .success = result {
switch result {
case .success:
print("Saved analytics for custom event with dimensions.")
case .failure(let error):
print(error)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ User.anonymous.login { result in
}

//: Convert the anonymous user to a real new user.
var currentUser2 = User.current?.mutable
var currentUser2 = User.current
currentUser2?.username = "bye"
currentUser2?.password = "world"
currentUser2?.signup { result in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ extension GameScore {
//: Define initial GameScores.
var score = GameScore(score: 40)

//: Set the ACL to default for your GameScore
score.ACL = try? ParseACL.defaultACL()

/*: Save asynchronously (preferred way) - Performs work on background
queue and returns to specified callbackQueue.
If no callbackQueue is specified it returns to main queue.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import ParseSwift
PlaygroundPage.current.needsIndefiniteExecution = true
initializeParse()

struct Installation: ParseInstallation, ParseObjectMutable {
struct Installation: ParseInstallation {
//: These are required by `ParseObject`.
var objectId: String?
var createdAt: Date?
Expand Down Expand Up @@ -61,7 +61,6 @@ currentInstallation?.save { results in
send the updated keys to the parse server as opposed to the
whole object.
*/
currentInstallation = currentInstallation?.mutable
currentInstallation?.customKey = "updatedValue"
currentInstallation?.save { results in

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ score.save { result in
assert(savedScore.objectId != nil)
assert(savedScore.createdAt != nil)
assert(savedScore.updatedAt != nil)
assert(savedScore.ACL == nil)
assert(savedScore.score == 10)
assert(savedScore.location != nil)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ score.save { result in
assert(savedScore.objectId != nil)
assert(savedScore.createdAt != nil)
assert(savedScore.updatedAt != nil)
assert(savedScore.ACL == nil)
assert(savedScore.score == 52)
assert(savedScore.profilePicture != nil)

Expand Down
121 changes: 79 additions & 42 deletions Sources/ParseSwift/API/API+Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,22 @@ internal extension API {
}

func execute(options: API.Options,
callbackQueue: DispatchQueue,
notificationQueue: DispatchQueue? = nil,
childObjects: [String: PointerType]? = nil,
childFiles: [UUID: ParseFile]? = nil,
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil) throws -> U {
var responseResult: Result<U, ParseError>?
let synchronizationQueue = DispatchQueue(label: "com.parse.Command.sync.\(UUID().uuidString)",
qos: .default,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil)
let group = DispatchGroup()
group.enter()
self.executeAsync(options: options,
callbackQueue: callbackQueue,
callbackQueue: synchronizationQueue,
notificationQueue: notificationQueue,
childObjects: childObjects,
childFiles: childFiles,
uploadProgress: uploadProgress,
Expand All @@ -102,29 +108,41 @@ internal extension API {
// swiftlint:disable:next function_body_length cyclomatic_complexity
func executeAsync(options: API.Options,
callbackQueue: DispatchQueue,
notificationQueue: DispatchQueue? = nil,
childObjects: [String: PointerType]? = nil,
childFiles: [UUID: ParseFile]? = nil,
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil,
completion: @escaping(Result<U, ParseError>) -> Void) {

let currentNotificationQueue: DispatchQueue!
if let notificationQueue = notificationQueue {
currentNotificationQueue = notificationQueue
} else {
currentNotificationQueue = callbackQueue
}
if !path.urlComponent.contains("/files/") {
//All ParseObjects use the shared URLSession
switch self.prepareURLRequest(options: options,
childObjects: childObjects,
childFiles: childFiles) {
case .success(let urlRequest):
URLSession.parse.dataTask(with: urlRequest, mapper: mapper) { result in
switch result {
URLSession.parse.dataTask(with: urlRequest,
callbackQueue: callbackQueue,
mapper: mapper) { result in
callbackQueue.async {
switch result {

case .success(let decoded):
completion(.success(decoded))
case .failure(let error):
completion(.failure(error))
case .success(let decoded):
completion(.success(decoded))
case .failure(let error):
completion(.failure(error))
}
}
}
case .failure(let error):
completion(.failure(error))
callbackQueue.async {
completion(.failure(error))
}
}
} else {
//ParseFiles are handled with a dedicated URLSession
Expand All @@ -137,40 +155,50 @@ internal extension API {

URLSession
.parse
.uploadTask(callbackQueue: callbackQueue,
.uploadTask(notificationQueue: currentNotificationQueue,
with: urlRequest,
from: uploadData,
from: uploadFile,
progress: uploadProgress,
mapper: mapper) { result in
switch result {

case .success(let decoded):
completion(.success(decoded))
case .failure(let error):
completion(.failure(error))
callbackQueue.async {
switch result {

case .success(let decoded):
completion(.success(decoded))
case .failure(let error):
completion(.failure(error))
}
}
}
}
case .failure(let error):
completion(.failure(error))
callbackQueue.async {
completion(.failure(error))
}
}
} else if method == .DELETE {

switch self.prepareURLRequest(options: options,
childObjects: childObjects,
childFiles: childFiles) {
case .success(let urlRequest):
URLSession.parse.dataTask(with: urlRequest, mapper: mapper) { result in
switch result {
URLSession.parse.dataTask(with: urlRequest,
callbackQueue: callbackQueue,
mapper: mapper) { result in
callbackQueue.async {
switch result {

case .success(let decoded):
completion(.success(decoded))
case .failure(let error):
completion(.failure(error))
case .success(let decoded):
completion(.success(decoded))
case .failure(let error):
completion(.failure(error))
}
}
}
case .failure(let error):
completion(.failure(error))
callbackQueue.async {
completion(.failure(error))
}
}

} else {
Expand All @@ -183,37 +211,46 @@ internal extension API {
case .success(let urlRequest):
URLSession
.parse
.downloadTask(callbackQueue: callbackQueue,
.downloadTask(notificationQueue: currentNotificationQueue,
with: urlRequest,
progress: downloadProgress,
mapper: mapper) { result in
switch result {

case .success(let decoded):
completion(.success(decoded))
case .failure(let error):
completion(.failure(error))
callbackQueue.async {
switch result {

case .success(let decoded):
completion(.success(decoded))
case .failure(let error):
completion(.failure(error))
}
}
}
}
case .failure(let error):
completion(.failure(error))
callbackQueue.async {
completion(.failure(error))
}
}
} else if let otherURL = self.otherURL {
//Non-parse servers don't receive any parse dedicated request info
var request = URLRequest(url: otherURL)
request.cachePolicy = requestCachePolicy(options: options)
URLSession.parse.downloadTask(with: request, mapper: mapper) { result in
switch result {
callbackQueue.async {
switch result {

case .success(let decoded):
completion(.success(decoded))
case .failure(let error):
completion(.failure(error))
case .success(let decoded):
completion(.success(decoded))
case .failure(let error):
completion(.failure(error))
}
}
}
} else {
completion(.failure(ParseError(code: .unknownError,
message: "Can't download the file without specifying the url")))
callbackQueue.async {
completion(.failure(ParseError(code: .unknownError,
// swiftlint:disable:next line_length
message: "Can't download the file without specifying the url")))
}
}
}
}
Expand Down
17 changes: 14 additions & 3 deletions Sources/ParseSwift/API/API+NonParseBodyCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,15 @@ internal extension API {

func execute(options: API.Options) throws -> U {
var responseResult: Result<U, ParseError>?
let synchronizationQueue = DispatchQueue(label: "com.parse.NonParseBodyCommand.sync.\(UUID().uuidString)",
qos: .default,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil)
let group = DispatchGroup()
group.enter()
self.executeAsync(options: options) { result in
self.executeAsync(options: options,
callbackQueue: synchronizationQueue) { result in
responseResult = result
group.leave()
}
Expand All @@ -50,11 +56,14 @@ internal extension API {

// MARK: Asynchronous Execution
func executeAsync(options: API.Options,
callbackQueue: DispatchQueue,
completion: @escaping(Result<U, ParseError>) -> Void) {

switch self.prepareURLRequest(options: options) {
case .success(let urlRequest):
URLSession.parse.dataTask(with: urlRequest, mapper: mapper) { result in
URLSession.parse.dataTask(with: urlRequest,
callbackQueue: callbackQueue,
mapper: mapper) { result in
switch result {

case .success(let decoded):
Expand All @@ -64,7 +73,9 @@ internal extension API {
}
}
case .failure(let error):
completion(.failure(error))
callbackQueue.async {
completion(.failure(error))
}
}
}

Expand Down
Loading

0 comments on commit 4734212

Please sign in to comment.