Skip to content

Commit dc7666f

Browse files
authored
fix: updating saved objects using saveAll (#423)
* initial deprecations * improve messages * fix: updating saved objects using saveAll * update to latest Xcode * fix changelog
1 parent e415f21 commit dc7666f

31 files changed

+497
-169
lines changed

.codecov.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ coverage:
66
status:
77
patch:
88
default:
9-
target: auto
9+
target: 58
1010
changes: false
1111
project:
1212
default:

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
env:
88
CI_XCODE_OLDEST: '/Applications/Xcode_12.5.1.app/Contents/Developer'
99
CI_XCODE_13: '/Applications/Xcode_13.4.1.app/Contents/Developer'
10-
CI_XCODE_LATEST: '/Applications/Xcode_14.0.app/Contents/Developer'
10+
CI_XCODE_LATEST: '/Applications/Xcode_14.0.1.app/Contents/Developer'
1111

1212
jobs:
1313
xcode-test-ios:

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
types: [published]
55
env:
66
CI_XCODE_13: '/Applications/Xcode_13.4.1.app/Contents/Developer'
7-
CI_XCODE_LATEST: '/Applications/Xcode_14.0.app/Contents/Developer'
7+
CI_XCODE_LATEST: '/Applications/Xcode_14.0.1.app/Contents/Developer'
88

99
jobs:
1010
cocoapods:

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
# Parse-Swift Changelog
22

33
### main
4-
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.14.1...main), [Documentation](https://swiftpackageindex.com/parse-community/Parse-Swift/main/documentation/parseswift)
4+
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.14.2...main), [Documentation](https://swiftpackageindex.com/parse-community/Parse-Swift/main/documentation/parseswift)
55
* _Contributing to this repo? Add info about your change here to be included in the next release_
66

7+
### 4.14.2
8+
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.14.1...4.14.2), [Documentation](https://swiftpackageindex.com/parse-community/Parse-Swift/4.14.2/documentation/parseswift)
9+
10+
__Fixes__
11+
- Addressed an issue that prevented updating ParseObjects with saveAll ([#423](https://github.com/parse-community/Parse-Swift/pull/423)), thanks to [Corey Baker](https://github.com/cbaker6).
12+
713
### 4.14.1
814
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.14.0...4.14.1), [Documentation](https://swiftpackageindex.com/parse-community/Parse-Swift/4.14.1/documentation/parseswift)
915

ParseSwift.playground/Pages/13 - Operations.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ do {
118118
//: There may be cases where you want to set/forceSet a value to null
119119
//: instead of unsetting
120120
let setToNullOperation = savedScore
121-
.operation.set(("name", \.name), value: nil)
121+
.operation.set(("name", \.name), to: nil)
122122
do {
123123
let updatedScore = try setToNullOperation.save()
124124
print("Updated score: \(updatedScore). Check the new score on Parse Dashboard.")

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ do {
5555
try score.location = ParseGeoPoint(latitude: 40.0, longitude: -30.0)
5656
}
5757

58-
/*: Save asynchronously (preferred way) - performs work on background
59-
queue and returns to specified callbackQueue.
60-
If no callbackQueue is specified it returns to main queue.
58+
/*:
59+
Save asynchronously (preferred way) - performs work on background
60+
queue and returns to specified callbackQueue.
61+
If no callbackQueue is specified it returns to main queue.
6162
*/
6263
score.save { result in
6364
switch result {
@@ -107,8 +108,9 @@ do {
107108
}
108109
}
109110

110-
/*: If you only want to query for points in descending order, use the order enum.
111-
Notice the "var", the query has to be mutable since it is a value type.
111+
/*:
112+
If you only want to query for points in descending order, use the order enum.
113+
Notice the "var", the query has to be mutable since it is a value type.
112114
*/
113115
var querySorted = query
114116
querySorted.order([.descending("points")])

ParseSwift.playground/Pages/8 - Pointers.xcplaygroundpage/Contents.swift

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ author.save { result in
108108
assert(savedAuthorAndBook.createdAt != nil)
109109
assert(savedAuthorAndBook.updatedAt != nil)
110110

111-
print("Saved \(savedAuthorAndBook)")
111+
print("Saved: \(savedAuthorAndBook)")
112112
case .failure(let error):
113113
assertionFailure("Error saving: \(error)")
114114
}
@@ -132,7 +132,7 @@ author2.save { result in
132132
Notice the pointer objects have not been updated on the
133133
client.If you want the latest pointer objects, fetch and include them.
134134
*/
135-
print("Saved \(savedAuthorAndBook)")
135+
print("Saved: \(savedAuthorAndBook)")
136136

137137
case .failure(let error):
138138
assertionFailure("Error saving: \(error)")
@@ -243,7 +243,7 @@ do {
243243
assert(updatedBook.updatedAt != nil)
244244
assert(updatedBook.relatedBook != nil)
245245

246-
print("Saved \(updatedBook)")
246+
print("Saved: \(updatedBook)")
247247
case .failure(let error):
248248
assertionFailure("Error saving: \(error)")
249249
}
@@ -323,12 +323,12 @@ author4.otherBooks = [otherBook3, otherBook4]
323323
assert(savedAuthorAndBook.createdAt != nil)
324324
assert(savedAuthorAndBook.updatedAt != nil)
325325
assert(savedAuthorAndBook.otherBooks?.count == 2)
326-
326+
author4 = savedAuthorAndBook
327327
/*:
328328
Notice the pointer objects have not been updated on the
329329
client.If you want the latest pointer objects, fetch and include them.
330330
*/
331-
print("Saved \(savedAuthorAndBook)")
331+
print("Saved: \(savedAuthorAndBook)")
332332
case .failure(let error):
333333
assertionFailure("Error saving: \(error)")
334334
}
@@ -339,5 +339,37 @@ author4.otherBooks = [otherBook3, otherBook4]
339339
}
340340
}
341341

342+
//: Batching saves by updating an already saved object.
343+
author4.fetch { result in
344+
switch result {
345+
case .success(var fetchedAuthor):
346+
print("The latest author: \(fetchedAuthor)")
347+
fetchedAuthor.name = "R.L. Stine"
348+
[fetchedAuthor].saveAll { result in
349+
switch result {
350+
case .success(let savedAuthorsAndBook):
351+
savedAuthorsAndBook.forEach { eachResult in
352+
switch eachResult {
353+
case .success(let savedAuthorAndBook):
354+
assert(savedAuthorAndBook.objectId != nil)
355+
assert(savedAuthorAndBook.createdAt != nil)
356+
assert(savedAuthorAndBook.updatedAt != nil)
357+
assert(savedAuthorAndBook.otherBooks?.count == 2)
358+
359+
print("Updated: \(savedAuthorAndBook)")
360+
case .failure(let error):
361+
assertionFailure("Error saving: \(error)")
362+
}
363+
}
364+
365+
case .failure(let error):
366+
assertionFailure("Error saving: \(error)")
367+
}
368+
}
369+
case .failure(let error):
370+
assertionFailure("Error fetching: \(error)")
371+
}
372+
}
373+
342374
PlaygroundPage.current.finishExecution()
343375
//: [Next](@next)

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ let profilePic = ParseFile(name: "profile.svg", cloudURL: linkToFile)
7474
//: Set the picture as part of your ParseObject
7575
score.profilePicture = profilePic
7676

77-
/*: Save asynchronously (preferred way) - Performs work on background
78-
queue and returns to specified callbackQueue.
79-
If no callbackQueue is specified it returns to main queue.
77+
/*:
78+
Save asynchronously (preferred way) - Performs work on background
79+
queue and returns to specified callbackQueue.
80+
If no callbackQueue is specified it returns to main queue.
8081
*/
8182
score.save { result in
8283
switch result {
@@ -121,7 +122,8 @@ score.save { result in
121122
}
122123
}
123124

124-
/*: Files can also be saved from data. Below is how to do it synchronously, but async is similar to above
125+
/*:
126+
Files can also be saved from data. Below is how to do it synchronously, but async is similar to above
125127
Create a new `ParseFile` for your data.
126128
*/
127129
let sampleData = "Hello World".data(using: .utf8)!
@@ -153,7 +155,8 @@ do {
153155
print("The file is now saved at: \(fetchedFile.localURL!)")
154156
print("The full details of your data ParseFile are: \(fetchedFile)")
155157

156-
/*: If you want to use the data from the file to display the text file or image, you need to retreive
158+
/*:
159+
If you want to use the data from the file to display the text file or image, you need to retreive
157160
the data from the file.
158161
*/
159162
guard let dataFromParseFile = try? Data(contentsOf: fetchedFile.localURL!) else {
@@ -179,7 +182,8 @@ do {
179182
fatalError("Error saving: \(error)")
180183
}
181184

182-
/*: Files can also be saved from files located on your device by using:
185+
/*:
186+
Files can also be saved from files located on your device by using:
183187
let localFile = ParseFile(name: "hello.txt", localURL: URL).
184188
*/
185189

Sources/ParseSwift/API/API+Command+async.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import FoundationNetworking
1515
internal extension API.Command {
1616
// MARK: Asynchronous Execution
1717
func executeAsync(options: API.Options,
18+
batching: Bool = false,
1819
callbackQueue: DispatchQueue,
1920
notificationQueue: DispatchQueue? = nil,
2021
childObjects: [String: PointerType]? = nil,
@@ -24,6 +25,7 @@ internal extension API.Command {
2425
downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil) async throws -> U {
2526
try await withCheckedThrowingContinuation { continuation in
2627
self.executeAsync(options: options,
28+
batching: batching,
2729
callbackQueue: callbackQueue,
2830
notificationQueue: notificationQueue,
2931
childObjects: childObjects,

Sources/ParseSwift/API/API+Command.swift

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ internal extension API {
5555
childFiles: [UUID: ParseFile]? = nil,
5656
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
5757
stream: InputStream) throws {
58-
switch self.prepareURLRequest(options: options, childObjects: childObjects, childFiles: childFiles) {
58+
switch self.prepareURLRequest(options: options,
59+
batching: false,
60+
childObjects: childObjects,
61+
childFiles: childFiles) {
5962

6063
case .success(let urlRequest):
6164
if method == .POST || method == .PUT || method == .PATCH {
@@ -80,6 +83,7 @@ internal extension API {
8083
}
8184

8285
func execute(options: API.Options,
86+
batching: Bool = false,
8387
notificationQueue: DispatchQueue? = nil,
8488
childObjects: [String: PointerType]? = nil,
8589
childFiles: [UUID: ParseFile]? = nil,
@@ -94,6 +98,7 @@ internal extension API {
9498
let group = DispatchGroup()
9599
group.enter()
96100
self.executeAsync(options: options,
101+
batching: batching,
97102
callbackQueue: synchronizationQueue,
98103
notificationQueue: notificationQueue,
99104
childObjects: childObjects,
@@ -115,6 +120,7 @@ internal extension API {
115120
// MARK: Asynchronous Execution
116121
// swiftlint:disable:next function_body_length cyclomatic_complexity
117122
func executeAsync(options: API.Options,
123+
batching: Bool = false,
118124
callbackQueue: DispatchQueue,
119125
notificationQueue: DispatchQueue? = nil,
120126
childObjects: [String: PointerType]? = nil,
@@ -131,6 +137,7 @@ internal extension API {
131137
if !path.urlComponent.contains("/files/") {
132138
// All ParseObjects use the shared URLSession
133139
switch self.prepareURLRequest(options: options,
140+
batching: batching,
134141
childObjects: childObjects,
135142
childFiles: childFiles) {
136143
case .success(let urlRequest):
@@ -156,6 +163,7 @@ internal extension API {
156163
// ParseFiles are handled with a dedicated URLSession
157164
if method == .POST || method == .PUT || method == .PATCH {
158165
switch self.prepareURLRequest(options: options,
166+
batching: batching,
159167
childObjects: childObjects,
160168
childFiles: childFiles) {
161169

@@ -187,6 +195,7 @@ internal extension API {
187195
} else if method == .DELETE {
188196

189197
switch self.prepareURLRequest(options: options,
198+
batching: batching,
190199
childObjects: childObjects,
191200
childFiles: childFiles) {
192201
case .success(let urlRequest):
@@ -213,6 +222,7 @@ internal extension API {
213222

214223
if parseURL != nil {
215224
switch self.prepareURLRequest(options: options,
225+
batching: batching,
216226
childObjects: childObjects,
217227
childFiles: childFiles) {
218228

@@ -266,6 +276,7 @@ internal extension API {
266276

267277
// MARK: URL Preperation
268278
func prepareURLRequest(options: API.Options,
279+
batching: Bool = false,
269280
childObjects: [String: PointerType]? = nil,
270281
childFiles: [UUID: ParseFile]? = nil) -> Result<URLRequest, ParseError> {
271282
let params = self.params?.getURLQueryItems()
@@ -299,7 +310,9 @@ internal extension API {
299310
} else {
300311
guard let bodyData = try? ParseCoding
301312
.parseEncoder()
302-
.encode(urlBody, collectChildren: false,
313+
.encode(urlBody,
314+
batching: batching,
315+
collectChildren: false,
303316
objectsSavedBeforeThisOne: childObjects,
304317
filesSavedBeforeThisOne: childFiles) else {
305318
return .failure(ParseError(code: .unknownError,
@@ -393,14 +406,16 @@ internal extension API.Command {
393406
// MARK: Saving ParseObjects
394407
static func save<T>(_ object: T,
395408
original data: Data?,
396-
ignoringCustomObjectIdConfig: Bool) throws -> API.Command<T, T> where T: ParseObject {
397-
if Parse.configuration.isAllowingCustomObjectIds
409+
ignoringCustomObjectIdConfig: Bool,
410+
batching: Bool = false) throws -> API.Command<T, T> where T: ParseObject {
411+
if Parse.configuration.isRequiringCustomObjectIds
398412
&& object.objectId == nil && !ignoringCustomObjectIdConfig {
399413
throw ParseError(code: .missingObjectId, message: "objectId must not be nil")
400414
}
401415
if object.isSaved {
402416
// MARK: Should be switched to "update" when server supports PATCH.
403-
return try replace(object, original: data)
417+
return try replace(object,
418+
original: data)
404419
}
405420
return create(object)
406421
}
@@ -420,7 +435,8 @@ internal extension API.Command {
420435
mapper: mapper)
421436
}
422437

423-
static func replace<T>(_ object: T, original data: Data?) throws -> API.Command<T, T> where T: ParseObject {
438+
static func replace<T>(_ object: T,
439+
original data: Data?) throws -> API.Command<T, T> where T: ParseObject {
424440
guard object.objectId != nil else {
425441
throw ParseError(code: .missingObjectId,
426442
message: "objectId must not be nil")
@@ -446,7 +462,8 @@ internal extension API.Command {
446462
mapper: mapper)
447463
}
448464

449-
static func update<T>(_ object: T, original data: Data?) throws -> API.Command<T, T> where T: ParseObject {
465+
static func update<T>(_ object: T,
466+
original data: Data?) throws -> API.Command<T, T> where T: ParseObject {
450467
guard object.objectId != nil else {
451468
throw ParseError(code: .missingObjectId,
452469
message: "objectId must not be nil")
@@ -504,8 +521,10 @@ internal extension API.Command where T: ParseObject {
504521
guard let body = command.body else {
505522
return nil
506523
}
507-
return API.Command<T, T>(method: command.method, path: .any(path),
508-
body: body, mapper: command.mapper)
524+
return API.Command<T, T>(method: command.method,
525+
path: .any(path),
526+
body: body,
527+
mapper: command.mapper)
509528
}
510529

511530
let mapper = { (data: Data) -> [Result<T, ParseError>] in

0 commit comments

Comments
 (0)