Skip to content

Commit 4734212

Browse files
authored
feat: retry URL connection on error (#291)
* feat: retry URL connection on error * fix linux and windows test * nits * fix acl and playgrounds * Fix errors in test cases * Improve
1 parent ae47862 commit 4734212

File tree

38 files changed

+534
-391
lines changed

38 files changed

+534
-391
lines changed

CHANGELOG.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,25 @@
22

33
### main
44

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

8+
### 2.3.0
9+
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.6...2.3.0)
10+
11+
__New features__
12+
- 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).
13+
- 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).
14+
15+
__Fixes__
16+
- 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).
17+
- 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).
18+
819
### 2.2.6
920
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.5...2.2.6)
1021

1122
__Fixes__
12-
- 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).
23+
- 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).
1324

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

ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ score.save { result in
9292
assert(savedScore.objectId != nil)
9393
assert(savedScore.createdAt != nil)
9494
assert(savedScore.updatedAt != nil)
95-
assert(savedScore.ACL == nil)
9695
assert(savedScore.score == 10)
9796

9897
/*: To modify, need to make it a var as the value type

ParseSwift.playground/Pages/15 - Custom ObjectId.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ score.save { result in
6060
assert(savedScore.objectId != nil)
6161
assert(savedScore.createdAt != nil)
6262
assert(savedScore.updatedAt != nil)
63-
assert(savedScore.ACL == nil)
6463
assert(savedScore.score == 10)
6564

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

ParseSwift.playground/Pages/16 - Analytics.xcplaygroundpage/Contents.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,32 @@ initializeParse()
1515

1616
//: To track when the app has been opened, do the following.
1717
ParseAnalytics.trackAppOpened { result in
18-
if case .success = result {
18+
switch result {
19+
case .success:
1920
print("Saved analytics for app opened.")
21+
case .failure(let error):
22+
print(error)
2023
}
2124
}
2225

2326
//: To track any event, do the following.
2427
var friendEvent = ParseAnalytics(name: "openedFriendList")
2528
friendEvent.track { result in
26-
if case .success = result {
29+
switch result {
30+
case .success:
2731
print("Saved analytics for custom event.")
32+
case .failure(let error):
33+
print(error)
2834
}
2935
}
3036

3137
//: You can also add dimensions to your analytics.
3238
friendEvent.track(dimensions: ["more": "info"]) { result in
33-
if case .success = result {
39+
switch result {
40+
case .success:
3441
print("Saved analytics for custom event with dimensions.")
42+
case .failure(let error):
43+
print(error)
3544
}
3645
}
3746

ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ User.anonymous.login { result in
210210
}
211211

212212
//: Convert the anonymous user to a real new user.
213-
var currentUser2 = User.current?.mutable
213+
var currentUser2 = User.current
214214
currentUser2?.username = "bye"
215215
currentUser2?.password = "world"
216216
currentUser2?.signup { result in

ParseSwift.playground/Pages/5 - ACL.xcplaygroundpage/Contents.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@ extension GameScore {
5050
//: Define initial GameScores.
5151
var score = GameScore(score: 40)
5252

53-
//: Set the ACL to default for your GameScore
54-
score.ACL = try? ParseACL.defaultACL()
55-
5653
/*: Save asynchronously (preferred way) - Performs work on background
5754
queue and returns to specified callbackQueue.
5855
If no callbackQueue is specified it returns to main queue.

ParseSwift.playground/Pages/6 - Installation.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import ParseSwift
1313
PlaygroundPage.current.needsIndefiniteExecution = true
1414
initializeParse()
1515

16-
struct Installation: ParseInstallation, ParseObjectMutable {
16+
struct Installation: ParseInstallation {
1717
//: These are required by `ParseObject`.
1818
var objectId: String?
1919
var createdAt: Date?
@@ -61,7 +61,6 @@ currentInstallation?.save { results in
6161
send the updated keys to the parse server as opposed to the
6262
whole object.
6363
*/
64-
currentInstallation = currentInstallation?.mutable
6564
currentInstallation?.customKey = "updatedValue"
6665
currentInstallation?.save { results in
6766

ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ score.save { result in
5151
assert(savedScore.objectId != nil)
5252
assert(savedScore.createdAt != nil)
5353
assert(savedScore.updatedAt != nil)
54-
assert(savedScore.ACL == nil)
5554
assert(savedScore.score == 10)
5655
assert(savedScore.location != nil)
5756

ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ score.save { result in
6262
assert(savedScore.objectId != nil)
6363
assert(savedScore.createdAt != nil)
6464
assert(savedScore.updatedAt != nil)
65-
assert(savedScore.ACL == nil)
6665
assert(savedScore.score == 52)
6766
assert(savedScore.profilePicture != nil)
6867

Sources/ParseSwift/API/API+Command.swift

Lines changed: 79 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,22 @@ internal extension API {
7272
}
7373

7474
func execute(options: API.Options,
75-
callbackQueue: DispatchQueue,
75+
notificationQueue: DispatchQueue? = nil,
7676
childObjects: [String: PointerType]? = nil,
7777
childFiles: [UUID: ParseFile]? = nil,
7878
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
7979
downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil) throws -> U {
8080
var responseResult: Result<U, ParseError>?
81+
let synchronizationQueue = DispatchQueue(label: "com.parse.Command.sync.\(UUID().uuidString)",
82+
qos: .default,
83+
attributes: .concurrent,
84+
autoreleaseFrequency: .inherit,
85+
target: nil)
8186
let group = DispatchGroup()
8287
group.enter()
8388
self.executeAsync(options: options,
84-
callbackQueue: callbackQueue,
89+
callbackQueue: synchronizationQueue,
90+
notificationQueue: notificationQueue,
8591
childObjects: childObjects,
8692
childFiles: childFiles,
8793
uploadProgress: uploadProgress,
@@ -102,29 +108,41 @@ internal extension API {
102108
// swiftlint:disable:next function_body_length cyclomatic_complexity
103109
func executeAsync(options: API.Options,
104110
callbackQueue: DispatchQueue,
111+
notificationQueue: DispatchQueue? = nil,
105112
childObjects: [String: PointerType]? = nil,
106113
childFiles: [UUID: ParseFile]? = nil,
107114
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
108115
downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil,
109116
completion: @escaping(Result<U, ParseError>) -> Void) {
110-
117+
let currentNotificationQueue: DispatchQueue!
118+
if let notificationQueue = notificationQueue {
119+
currentNotificationQueue = notificationQueue
120+
} else {
121+
currentNotificationQueue = callbackQueue
122+
}
111123
if !path.urlComponent.contains("/files/") {
112124
//All ParseObjects use the shared URLSession
113125
switch self.prepareURLRequest(options: options,
114126
childObjects: childObjects,
115127
childFiles: childFiles) {
116128
case .success(let urlRequest):
117-
URLSession.parse.dataTask(with: urlRequest, mapper: mapper) { result in
118-
switch result {
129+
URLSession.parse.dataTask(with: urlRequest,
130+
callbackQueue: callbackQueue,
131+
mapper: mapper) { result in
132+
callbackQueue.async {
133+
switch result {
119134

120-
case .success(let decoded):
121-
completion(.success(decoded))
122-
case .failure(let error):
123-
completion(.failure(error))
135+
case .success(let decoded):
136+
completion(.success(decoded))
137+
case .failure(let error):
138+
completion(.failure(error))
139+
}
124140
}
125141
}
126142
case .failure(let error):
127-
completion(.failure(error))
143+
callbackQueue.async {
144+
completion(.failure(error))
145+
}
128146
}
129147
} else {
130148
//ParseFiles are handled with a dedicated URLSession
@@ -137,40 +155,50 @@ internal extension API {
137155

138156
URLSession
139157
.parse
140-
.uploadTask(callbackQueue: callbackQueue,
158+
.uploadTask(notificationQueue: currentNotificationQueue,
141159
with: urlRequest,
142160
from: uploadData,
143161
from: uploadFile,
144162
progress: uploadProgress,
145163
mapper: mapper) { result in
146-
switch result {
147-
148-
case .success(let decoded):
149-
completion(.success(decoded))
150-
case .failure(let error):
151-
completion(.failure(error))
164+
callbackQueue.async {
165+
switch result {
166+
167+
case .success(let decoded):
168+
completion(.success(decoded))
169+
case .failure(let error):
170+
completion(.failure(error))
171+
}
172+
}
152173
}
153-
}
154174
case .failure(let error):
155-
completion(.failure(error))
175+
callbackQueue.async {
176+
completion(.failure(error))
177+
}
156178
}
157179
} else if method == .DELETE {
158180

159181
switch self.prepareURLRequest(options: options,
160182
childObjects: childObjects,
161183
childFiles: childFiles) {
162184
case .success(let urlRequest):
163-
URLSession.parse.dataTask(with: urlRequest, mapper: mapper) { result in
164-
switch result {
185+
URLSession.parse.dataTask(with: urlRequest,
186+
callbackQueue: callbackQueue,
187+
mapper: mapper) { result in
188+
callbackQueue.async {
189+
switch result {
165190

166-
case .success(let decoded):
167-
completion(.success(decoded))
168-
case .failure(let error):
169-
completion(.failure(error))
191+
case .success(let decoded):
192+
completion(.success(decoded))
193+
case .failure(let error):
194+
completion(.failure(error))
195+
}
170196
}
171197
}
172198
case .failure(let error):
173-
completion(.failure(error))
199+
callbackQueue.async {
200+
completion(.failure(error))
201+
}
174202
}
175203

176204
} else {
@@ -183,37 +211,46 @@ internal extension API {
183211
case .success(let urlRequest):
184212
URLSession
185213
.parse
186-
.downloadTask(callbackQueue: callbackQueue,
214+
.downloadTask(notificationQueue: currentNotificationQueue,
187215
with: urlRequest,
188216
progress: downloadProgress,
189217
mapper: mapper) { result in
190-
switch result {
191-
192-
case .success(let decoded):
193-
completion(.success(decoded))
194-
case .failure(let error):
195-
completion(.failure(error))
218+
callbackQueue.async {
219+
switch result {
220+
221+
case .success(let decoded):
222+
completion(.success(decoded))
223+
case .failure(let error):
224+
completion(.failure(error))
225+
}
226+
}
196227
}
197-
}
198228
case .failure(let error):
199-
completion(.failure(error))
229+
callbackQueue.async {
230+
completion(.failure(error))
231+
}
200232
}
201233
} else if let otherURL = self.otherURL {
202234
//Non-parse servers don't receive any parse dedicated request info
203235
var request = URLRequest(url: otherURL)
204236
request.cachePolicy = requestCachePolicy(options: options)
205237
URLSession.parse.downloadTask(with: request, mapper: mapper) { result in
206-
switch result {
238+
callbackQueue.async {
239+
switch result {
207240

208-
case .success(let decoded):
209-
completion(.success(decoded))
210-
case .failure(let error):
211-
completion(.failure(error))
241+
case .success(let decoded):
242+
completion(.success(decoded))
243+
case .failure(let error):
244+
completion(.failure(error))
245+
}
212246
}
213247
}
214248
} else {
215-
completion(.failure(ParseError(code: .unknownError,
216-
message: "Can't download the file without specifying the url")))
249+
callbackQueue.async {
250+
completion(.failure(ParseError(code: .unknownError,
251+
// swiftlint:disable:next line_length
252+
message: "Can't download the file without specifying the url")))
253+
}
217254
}
218255
}
219256
}

0 commit comments

Comments
 (0)