Skip to content

Commit 675168e

Browse files
authored
fix: encode pointer object properties properly when batching (#390)
* reduce loops in batching calls * fix saveAll async * fix: encode pointer object properties properly when batching
1 parent 50f655b commit 675168e

File tree

11 files changed

+428
-98
lines changed

11 files changed

+428
-98
lines changed

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

+68-1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ let otherBook1 = Book(title: "I like this book")
113113
let otherBook2 = Book(title: "I like this book also")
114114
var author2 = Author(name: "Bruce", book: newBook)
115115
author2.otherBooks = [otherBook1, otherBook2]
116+
116117
author2.save { result in
117118
switch result {
118119
case .success(let savedAuthorAndBook):
@@ -121,7 +122,10 @@ author2.save { result in
121122
assert(savedAuthorAndBook.updatedAt != nil)
122123
assert(savedAuthorAndBook.otherBooks?.count == 2)
123124

124-
//: Notice the pointer objects haven't been updated on the client.
125+
/*:
126+
Notice the pointer objects haven't been updated on the
127+
client.If you want the latest pointer objects, fetch and include them.
128+
*/
125129
print("Saved \(savedAuthorAndBook)")
126130

127131
case .failure(let error):
@@ -262,5 +266,68 @@ do {
262266
print("\(error)")
263267
}
264268

269+
//: Batching saves with saved and unsaved pointer items.
270+
var author3 = Author(name: "Logan", book: newBook)
271+
let otherBook3 = Book(title: "I like this book")
272+
let otherBook4 = Book(title: "I like this book also")
273+
author3.otherBooks = [otherBook3, otherBook4]
274+
275+
[author3].saveAll { result in
276+
switch result {
277+
case .success(let savedAuthorsAndBook):
278+
savedAuthorsAndBook.forEach { eachResult in
279+
switch eachResult {
280+
case .success(let savedAuthorAndBook):
281+
assert(savedAuthorAndBook.objectId != nil)
282+
assert(savedAuthorAndBook.createdAt != nil)
283+
assert(savedAuthorAndBook.updatedAt != nil)
284+
assert(savedAuthorAndBook.otherBooks?.count == 2)
285+
286+
/*:
287+
Notice the pointer objects haven't been updated on the
288+
client.If you want the latest pointer objects, fetch and include them.
289+
*/
290+
print("Saved \(savedAuthorAndBook)")
291+
case .failure(let error):
292+
assertionFailure("Error saving: \(error)")
293+
}
294+
}
295+
296+
case .failure(let error):
297+
assertionFailure("Error saving: \(error)")
298+
}
299+
}
300+
301+
//: Batching saves with unsaved pointer items.
302+
var newBook2 = Book(title: "world")
303+
var author4 = Author(name: "Scott", book: newBook2)
304+
author4.otherBooks = [otherBook3, otherBook4]
305+
306+
[author4].saveAll { result in
307+
switch result {
308+
case .success(let savedAuthorsAndBook):
309+
savedAuthorsAndBook.forEach { eachResult in
310+
switch eachResult {
311+
case .success(let savedAuthorAndBook):
312+
assert(savedAuthorAndBook.objectId != nil)
313+
assert(savedAuthorAndBook.createdAt != nil)
314+
assert(savedAuthorAndBook.updatedAt != nil)
315+
assert(savedAuthorAndBook.otherBooks?.count == 2)
316+
317+
/*:
318+
Notice the pointer objects haven't been updated on the
319+
client.If you want the latest pointer objects, fetch and include them.
320+
*/
321+
print("Saved \(savedAuthorAndBook)")
322+
case .failure(let error):
323+
assertionFailure("Error saving: \(error)")
324+
}
325+
}
326+
327+
case .failure(let error):
328+
assertionFailure("Error saving: \(error)")
329+
}
330+
}
331+
265332
PlaygroundPage.current.finishExecution()
266333
//: [Next](@next)

ParseSwift.xcodeproj/project.pbxproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1619,8 +1619,8 @@
16191619
7003963A25A288100052CB31 /* ParseLiveQueryTests.swift */,
16201620
703B091A26BDE774005A112F /* ParseObjectAsyncTests.swift */,
16211621
70C7DC2024D20F190050419B /* ParseObjectBatchTests.swift */,
1622-
7044C1DE25C5C70D0011F6E7 /* ParseObjectCombineTests.swift */,
16231622
70732C592606CCAD000CAB81 /* ParseObjectCustomObjectIdTests.swift */,
1623+
7044C1DE25C5C70D0011F6E7 /* ParseObjectCombineTests.swift */,
16241624
911DB13524C4FC100027F3C7 /* ParseObjectTests.swift */,
16251625
917BA43D2703E84000F8D747 /* ParseOperationAsyncTests.swift */,
16261626
7044C1EB25C5CC930011F6E7 /* ParseOperationCombineTests.swift */,

Sources/ParseSwift/API/API+Command.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ internal extension API.Command where T: ParseObject {
576576
}
577577
}
578578

579-
let batchCommand = BatchCommandNoBody(requests: commands, transaction: transaction)
579+
let batchCommand = BatchCommandEncodable(requests: commands, transaction: transaction)
580580
return RESTBatchCommandNoBodyType<NoBody>(method: .POST, path: .batch, body: batchCommand, mapper: mapper)
581581
}
582582
}

Sources/ParseSwift/API/API+NonParseBodyCommand.swift

+2-3
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ internal extension API.NonParseBodyCommand {
152152
internal extension API.NonParseBodyCommand {
153153
// MARK: Batch - Child Objects
154154
static func batch(objects: [ParseEncodable],
155-
transaction: Bool) throws -> RESTBatchCommandTypeEncodable<AnyCodable> {
155+
transaction: Bool) throws -> RESTBatchCommandTypeEncodablePointer<AnyCodable> {
156156
let batchCommands = try objects.compactMap { (object) -> API.BatchCommand<AnyCodable, PointerType>? in
157157
guard var objectable = object as? Objectable else {
158158
return nil
@@ -193,7 +193,6 @@ internal extension API.NonParseBodyCommand {
193193
guard let parseError = response.error else {
194194
return .failure(ParseError(code: .unknownError, message: "unknown error"))
195195
}
196-
197196
return .failure(parseError)
198197
}
199198
})
@@ -206,7 +205,7 @@ internal extension API.NonParseBodyCommand {
206205
}
207206
let batchCommand = BatchChildCommand(requests: batchCommands,
208207
transaction: transaction)
209-
return RESTBatchCommandTypeEncodable<AnyCodable>(method: .POST,
208+
return RESTBatchCommandTypeEncodablePointer<AnyCodable>(method: .POST,
210209
path: .batch,
211210
body: batchCommand,
212211
mapper: mapper)

Sources/ParseSwift/API/BatchUtils.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,22 @@ typealias ParseObjectBatchResponse<T> = [(Result<T, ParseError>)]
1313
// swiftlint:disable line_length
1414
typealias RESTBatchCommandType<T> = API.Command<ParseObjectBatchCommand<T>, ParseObjectBatchResponse<T>> where T: ParseObject
1515

16-
typealias ParseObjectBatchCommandNoBody<T> = BatchCommandNoBody<NoBody, NoBody>
16+
typealias ParseObjectBatchCommandNoBody<T> = BatchCommandEncodable<NoBody, NoBody>
1717
typealias ParseObjectBatchResponseNoBody<NoBody> = [(Result<Void, ParseError>)]
1818
typealias RESTBatchCommandNoBodyType<T> = API.NonParseBodyCommand<ParseObjectBatchCommandNoBody<T>, ParseObjectBatchResponseNoBody<T>> where T: Encodable
1919

20-
typealias ParseObjectBatchCommandEncodable<T> = BatchChildCommand<T, PointerType> where T: Encodable
21-
typealias ParseObjectBatchResponseEncodable<U> = [(Result<PointerType, ParseError>)]
20+
typealias ParseObjectBatchCommandEncodablePointer<T> = BatchChildCommand<T, PointerType> where T: Encodable
21+
typealias ParseObjectBatchResponseEncodablePointer<U> = [(Result<PointerType, ParseError>)]
2222
// swiftlint:disable line_length
23-
typealias RESTBatchCommandTypeEncodable<T> = API.NonParseBodyCommand<ParseObjectBatchCommandEncodable<T>, ParseObjectBatchResponseEncodable<Encodable>> where T: Encodable
23+
typealias RESTBatchCommandTypeEncodablePointer<T> = API.NonParseBodyCommand<ParseObjectBatchCommandEncodablePointer<T>, ParseObjectBatchResponseEncodablePointer<Encodable>> where T: Encodable
2424
// swiftlint:enable line_length
2525

2626
internal struct BatchCommand<T, U>: ParseEncodable where T: ParseEncodable {
2727
let requests: [API.Command<T, U>]
2828
var transaction: Bool
2929
}
3030

31-
internal struct BatchCommandNoBody<T, U>: Encodable where T: Encodable {
31+
internal struct BatchCommandEncodable<T, U>: Encodable where T: Encodable {
3232
let requests: [API.NonParseBodyCommand<T, U>]
3333
var transaction: Bool
3434
}

Sources/ParseSwift/Coding/ParseEncoder.swift

+3-11
Original file line numberDiff line numberDiff line change
@@ -340,23 +340,15 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
340340
throw ParseError(code: .unknownError,
341341
message: "Found a circular dependency when encoding.")
342342
}
343-
if !self.collectChildren && codingPath.count > 0 {
344-
valueToEncode = value
345-
} else {
346-
valueToEncode = pointer
347-
}
343+
valueToEncode = pointer
348344
} else if let object = value as? Objectable {
349345
if let pointer = try? PointerType(object) {
350346
if let uniquePointer = self.uniquePointer,
351347
uniquePointer.hasSameObjectId(as: pointer) {
352348
throw ParseError(code: .unknownError,
353349
message: "Found a circular dependency when encoding.")
354350
}
355-
if !self.collectChildren && codingPath.count > 0 {
356-
valueToEncode = value
357-
} else {
358-
valueToEncode = pointer
359-
}
351+
valueToEncode = pointer
360352
} else {
361353
var object = object
362354
if object.ACL == nil,
@@ -959,7 +951,7 @@ extension _ParseEncoder {
959951
// swiftlint:disable:next force_cast
960952
return (value as! NSDecimalNumber)
961953
} else if value is _JSONStringDictionaryEncodableMarker {
962-
//COREY: DON'T remove the force unwrap, it will crash the app
954+
// COREY: DON'T remove the force cast, it will crash the app
963955
// swiftlint:disable:next force_cast
964956
return try self.box(value as! [String : Encodable])
965957
} else if value is ParsePointer {

Sources/ParseSwift/Objects/ParseInstallation.swift

+36-23
Original file line numberDiff line numberDiff line change
@@ -945,14 +945,17 @@ public extension Sequence where Element: ParseInstallation {
945945
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
946946
var childObjects = [String: PointerType]()
947947
var childFiles = [UUID: ParseFile]()
948+
var commands = [API.Command<Self.Element, Self.Element>]()
948949
var error: ParseError?
949950

950-
let installations = map { $0 }
951-
for installation in installations {
951+
try forEach {
952+
let installation = $0
952953
let group = DispatchGroup()
953954
group.enter()
954-
installation.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in
955-
//If an error occurs, everything should be skipped
955+
installation.ensureDeepSave(options: options,
956+
// swiftlint:disable:next line_length
957+
isShouldReturnIfChildObjectsFound: transaction) { (savedChildObjects, savedChildFiles, parseError) -> Void in
958+
// If an error occurs, everything should be skipped
956959
if parseError != nil {
957960
error = parseError
958961
}
@@ -984,12 +987,10 @@ public extension Sequence where Element: ParseInstallation {
984987
if let error = error {
985988
throw error
986989
}
990+
commands.append(try installation.saveCommand(ignoringCustomObjectIdConfig: ignoringCustomObjectIdConfig))
987991
}
988992

989993
var returnBatch = [(Result<Self.Element, ParseError>)]()
990-
let commands = try map {
991-
try $0.saveCommand(ignoringCustomObjectIdConfig: ignoringCustomObjectIdConfig)
992-
}
993994
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
994995
try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit)
995996
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
@@ -1171,16 +1172,17 @@ public extension Sequence where Element: ParseInstallation {
11711172
var childObjects = [String: PointerType]()
11721173
var childFiles = [UUID: ParseFile]()
11731174
var error: ParseError?
1174-
1175+
var commands = [API.Command<Self.Element, Self.Element>]()
11751176
let installations = map { $0 }
1177+
11761178
for installation in installations {
11771179
let group = DispatchGroup()
11781180
group.enter()
11791181
installation
11801182
.ensureDeepSave(options: options,
11811183
// swiftlint:disable:next line_length
1182-
isShouldReturnIfChildObjectsFound: true) { (savedChildObjects, savedChildFiles, parseError) -> Void in
1183-
//If an error occurs, everything should be skipped
1184+
isShouldReturnIfChildObjectsFound: transaction) { (savedChildObjects, savedChildFiles, parseError) -> Void in
1185+
// If an error occurs, everything should be skipped
11841186
if parseError != nil {
11851187
error = parseError
11861188
}
@@ -1215,23 +1217,34 @@ public extension Sequence where Element: ParseInstallation {
12151217
}
12161218
return
12171219
}
1220+
1221+
do {
1222+
switch method {
1223+
case .save:
1224+
commands.append(
1225+
try installation.saveCommand(ignoringCustomObjectIdConfig: ignoringCustomObjectIdConfig)
1226+
)
1227+
case .create:
1228+
commands.append(installation.createCommand())
1229+
case .replace:
1230+
commands.append(try installation.replaceCommand())
1231+
case .update:
1232+
commands.append(try installation.updateCommand())
1233+
}
1234+
} catch {
1235+
callbackQueue.async {
1236+
if let parseError = error as? ParseError {
1237+
completion(.failure(parseError))
1238+
} else {
1239+
completion(.failure(.init(code: .unknownError, message: error.localizedDescription)))
1240+
}
1241+
}
1242+
return
1243+
}
12181244
}
12191245

12201246
do {
12211247
var returnBatch = [(Result<Self.Element, ParseError>)]()
1222-
let commands: [API.Command<Self.Element, Self.Element>]!
1223-
switch method {
1224-
case .save:
1225-
commands = try map {
1226-
try $0.saveCommand(ignoringCustomObjectIdConfig: ignoringCustomObjectIdConfig)
1227-
}
1228-
case .create:
1229-
commands = map { $0.createCommand() }
1230-
case .replace:
1231-
commands = try map { try $0.replaceCommand() }
1232-
case .update:
1233-
commands = try map { try $0.updateCommand() }
1234-
}
12351248

12361249
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
12371250
try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit)

0 commit comments

Comments
 (0)