Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: retry URL connection on error #291

Merged
merged 6 commits into from
Nov 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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