@capacitor-community/sqlite 5.7.2 → 5.7.3-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -589,12 +589,9 @@ enum CapacitorSQLiteError: Error {
589
589
  self.retHandler.rResult(call: call)
590
590
  return
591
591
  case .failure(let error):
592
-
593
- if error == .downloadFromHTTPFailed {
594
- let msg = "Download from HTTP failed"
595
- self.retHandler.rResult(call: call, message: msg)
596
- return
597
- }
592
+ let msg = "Download from HTTP failed: \(error.localizedDescription)"
593
+ self.retHandler.rResult(call: call, message: msg)
594
+ return
598
595
 
599
596
  }
600
597
 
@@ -1013,6 +1010,7 @@ enum CapacitorSQLiteError: Error {
1013
1010
  // MARK: - deleteDatabase
1014
1011
 
1015
1012
  // swiftlint:disable cyclomatic_complexity
1013
+ // swiftlint:disable function_body_length
1016
1014
  @objc func deleteDatabase(_ dbName: String, readonly: Bool) throws {
1017
1015
  guard isInit else {
1018
1016
  throw CapacitorSQLiteError.failed(message: initMessage)
@@ -1030,10 +1028,12 @@ enum CapacitorSQLiteError: Error {
1030
1028
  do {
1031
1029
  if !mDb.isDBOpen() {
1032
1030
  // check the state of the DB
1033
- let state: State = UtilsSQLCipher.getDatabaseState(databaseLocation: databaseLocation, databaseName: "\(mDbName)SQLite.db", account: account)
1034
- if !isEncryption &&
1035
- (state.rawValue == "ENCRYPTEDGLOBALSECRET" ||
1036
- state.rawValue == "ENCRYPTEDSECRET") {
1031
+ let state: State = UtilsSQLCipher.getDatabaseState(databaseLocation: databaseLocation,
1032
+ databaseName: "\(mDbName)SQLite.db",
1033
+ account: account)
1034
+ if !isEncryption &&
1035
+ (state.rawValue == "ENCRYPTEDGLOBALSECRET" ||
1036
+ state.rawValue == "ENCRYPTEDSECRET") {
1037
1037
  var msg = "Cannot delete an Encrypted database with "
1038
1038
  msg += "No Encryption set in capacitor.config"
1039
1039
  throw CapacitorSQLiteError.failed(message: msg)
@@ -1066,6 +1066,7 @@ enum CapacitorSQLiteError: Error {
1066
1066
  throw CapacitorSQLiteError.failed(message: msg)
1067
1067
  }
1068
1068
  }
1069
+ // swiftlint:enable function_body_length
1069
1070
  // swiftlint:enable cyclomatic_complexity
1070
1071
 
1071
1072
  // MARK: - isJsonValid
@@ -1547,7 +1548,7 @@ enum CapacitorSQLiteError: Error {
1547
1548
  // get the database files
1548
1549
  let dbList: [String] = try UtilsFile.getFileList(path: aPath, ext: ".db")
1549
1550
  // filter for db including SQLite
1550
- let dbWithSQLite = dbList.filter { $0.contains("SQLite") }
1551
+ let dbWithSQLite = dbList.filter({ $0.contains("SQLite") })
1551
1552
 
1552
1553
  return dbWithSQLite
1553
1554
 
@@ -1567,7 +1568,7 @@ enum CapacitorSQLiteError: Error {
1567
1568
  let dbList: [String] = try UtilsMigrate
1568
1569
  .getMigratableList(folderPath: folderPath)
1569
1570
  // filter for db not including SQLite
1570
- let dbNoSQLite = dbList.filter { !$0.contains("SQLite") }
1571
+ let dbNoSQLite = dbList.filter({ !$0.contains("SQLite") })
1571
1572
 
1572
1573
  return dbNoSQLite
1573
1574
 
@@ -1369,14 +1369,13 @@ public class CapacitorSQLitePlugin: CAPPlugin {
1369
1369
  implementation?.addUpgradeStatement(dbName,
1370
1370
  upgrade: upgrade) {
1371
1371
  if
1372
- versionUpgrades[dbName] != nil {
1372
+ self.versionUpgrades["\(dbName)"] != nil {
1373
1373
  for (versionKey, upgObj) in upgVersionDict {
1374
- versionUpgrades[dbName]?[versionKey] = upgObj
1374
+ self.versionUpgrades["\(dbName)"]?[versionKey] = upgObj
1375
1375
  }
1376
1376
  } else {
1377
- versionUpgrades = ["\(dbName)": upgVersionDict]
1377
+ self.versionUpgrades["\(dbName)"] = upgVersionDict
1378
1378
  }
1379
-
1380
1379
  retHandler.rResult(call: call)
1381
1380
  return
1382
1381
  } else {
@@ -1542,29 +1541,25 @@ public class CapacitorSQLitePlugin: CAPPlugin {
1542
1541
  message: "GetFromHTTPRequest: Must provide a database url")
1543
1542
  return
1544
1543
  }
1545
- DispatchQueue.global(qos: .background).async {
1544
+ DispatchQueue.global(qos: .background).async(execute: {
1546
1545
  do {
1547
1546
  try self.implementation?.getFromHTTPRequest(call, url: url)
1548
- DispatchQueue.main.async {
1549
- self.retHandler.rResult(call: call)
1550
- return
1551
- }
1552
1547
  } catch CapacitorSQLiteError.failed(let message) {
1553
1548
 
1554
- DispatchQueue.main.async {
1549
+ DispatchQueue.main.async(execute: {
1555
1550
  let msg = "GetFromHTTPRequest: \(message)"
1556
1551
  self.retHandler.rResult(call: call, message: msg)
1557
1552
  return
1558
- }
1553
+ })
1559
1554
  } catch let error {
1560
- DispatchQueue.main.async {
1555
+ DispatchQueue.main.async(execute: {
1561
1556
  let msg = "GetFromHTTPRequest: " +
1562
1557
  "\(error.localizedDescription)"
1563
1558
  self.retHandler.rResult(call: call, message: msg)
1564
1559
  return
1565
- }
1560
+ })
1566
1561
  }
1567
- }
1562
+ })
1568
1563
 
1569
1564
  }
1570
1565
 
@@ -84,10 +84,9 @@ class UtilsBinding {
84
84
  // swiftlint:enable cyclomatic_complexity
85
85
  class func extractSortedValues(from queryValues: [String: Int]) -> [UInt8] {
86
86
  // Extract keys and sort them
87
- let sortedKeys = queryValues.keys.sorted { $0.localizedStandardCompare($1) == .orderedAscending }
88
-
87
+ let sortedKeys = queryValues.keys.sorted { $0.localizedStandardCompare($1) == .orderedAscending}
89
88
  // Extract corresponding values and sort them based on keys
90
- let sortedValues = sortedKeys.compactMap { UInt8(queryValues[$0] ?? 0) }
89
+ let sortedValues = sortedKeys.compactMap({ UInt8(queryValues[$0] ?? 0) })
91
90
 
92
91
  return sortedValues
93
92
  }
@@ -289,19 +289,19 @@ class UtilsDelete {
289
289
  throws -> (String, [[String: Any]]) {
290
290
  var relatedItems: [[String: Any]] = []
291
291
  var key: String = ""
292
- let t1Names = withRefsNames.map { "t1.\($0)" }
293
- let t2Names = colNames.map { "t2.\($0)" }
292
+ let t1Names = withRefsNames.map({ "t1.\($0)" })
293
+ let t2Names = colNames.map({ "t2.\($0)"})
294
294
 
295
295
  do {
296
296
  var whereClause = try UtilsSQLStatement
297
297
  .addPrefixToWhereClause(whStmt, from: colNames,
298
- to: withRefsNames,
298
+ destination: withRefsNames,
299
299
  prefix: "t2.")
300
300
  if whereClause.hasSuffix(";") {
301
301
  whereClause = String(whereClause.dropLast())
302
302
  }
303
303
  let resultString = zip(t1Names, t2Names)
304
- .map { "\($0) = \($1)" }
304
+ .map({ "\($0) = \($1)"})
305
305
  .joined(separator: " AND ")
306
306
  let sql = "SELECT t1.rowid FROM \(updTableName) t1 " +
307
307
  "JOIN \(tableName) t2 ON \(resultString) " +
@@ -312,9 +312,12 @@ class UtilsDelete {
312
312
  if let mVals = vals[0]["ios_columns"] as? [String] {
313
313
  key = mVals[0]
314
314
  let keyToRemove = "ios_columns"
315
- vals.removeAll { dict in
315
+
316
+ // Remove dictionaries where the keys contain "ios_columns"
317
+ vals.removeAll { (dict: [String: Any]) in
316
318
  return dict.keys.contains(keyToRemove)
317
319
  }
320
+ // Append the remaining dictionaries to relatedItems
318
321
  for val in vals {
319
322
  relatedItems.append(val)
320
323
  }
@@ -440,9 +443,15 @@ class UtilsDelete {
440
443
  .firstMatch(in: statement, options: [],
441
444
  range: NSRange(location: 0,
442
445
  length: statement.utf16.count)) {
443
- let tableNameRange = Range(tableNameMatch.range(
444
- at: 1), in: statement)!
445
- tableName = String(statement[tableNameRange])
446
+ if let tableNameRange = Range(tableNameMatch.range(at: 1), in: statement) {
447
+ tableName = String(statement[tableNameRange])
448
+ } else {
449
+ let msg = "getRefs: Error creating tableNameRange "
450
+ throw UtilsDeleteError.getRefs(message: msg)
451
+ }
452
+ } else {
453
+ let msg = "getRefs: Error creating tableNameMatch "
454
+ throw UtilsDeleteError.getRefs(message: msg)
446
455
  }
447
456
 
448
457
  // Regular expression pattern to match the FOREIGN KEY
@@ -458,10 +467,14 @@ class UtilsDelete {
458
467
  range: NSRange(location: 0,
459
468
  length: statement.utf16.count))
460
469
  for foreignKeyMatch in foreignKeyMatches {
461
- let foreignKeyRange = Range(
462
- foreignKeyMatch.range(at: 0), in: statement)!
463
- let foreignKey = String(statement[foreignKeyRange])
464
- foreignKeys.append(foreignKey)
470
+ if let foreignKeyRange = Range(
471
+ foreignKeyMatch.range(at: 0), in: statement) {
472
+ let foreignKey = String(statement[foreignKeyRange])
473
+ foreignKeys.append(foreignKey)
474
+ } else {
475
+ let msg = "getRefs: Error creating foreignKeyRange "
476
+ throw UtilsDeleteError.getRefs(message: msg)
477
+ }
465
478
  }
466
479
  } catch {
467
480
  let msg = "getRefs: Error creating regular expression: " +
@@ -9,114 +9,184 @@ import Foundation
9
9
  import ZIPFoundation
10
10
 
11
11
  enum UtilsDownloadError: Error {
12
- case downloadFromHTTPFailed
12
+ case downloadFromHTTPFailed(message: String)
13
+ case fileNotFound(message: String)
14
+ case fileMoveFailed(message: String)
15
+ case fileExtractionFailed(message: String)
16
+ case invalidArchive(message: String)
13
17
  }
18
+
14
19
  class UtilsDownloadFromHTTP {
15
- // swiftlint:disable cyclomatic_complexity
16
- // swiftlint:disable function_body_length
20
+
17
21
  class func download(databaseLocation: String, url: String,
18
- completion: @escaping (Result<Bool, UtilsDownloadError>)
19
- -> Void) {
20
- if let mUrl: URL = URL(string: url) {
21
- let fileName = mUrl.lastPathComponent
22
- var dbName = fileName
23
- var isZip = false
24
- if !fileName.contains("SQLite.db") {
25
- if String(fileName.suffix(3)) == ".db" {
26
- dbName = fileName.replacingOccurrences(
27
- of: ".db", with: "SQLite.db")
28
- }
29
- if String(fileName.suffix(4)) == ".zip" {
30
- isZip = true
22
+ completion: @escaping (Result<Bool, UtilsDownloadError>) -> Void) {
23
+ guard let fileDetails = getFileDetails(url: url),
24
+ let fileExtension = fileDetails.extension ,
25
+ fileExtension == "db" || fileExtension == "zip" else {
26
+ let msg = "download: Not a .zip or .db url"
27
+ print("\(msg)")
28
+ completion(.failure(UtilsDownloadError.downloadFromHTTPFailed(message: msg)))
29
+ return
30
+ }
31
+
32
+ guard let dbUrl = try? UtilsFile.getDatabaseLocationURL(databaseLocation: databaseLocation) else {
33
+ let msg = "databaseLocation failed"
34
+ print("\(msg)")
35
+ completion(.failure(UtilsDownloadError.downloadFromHTTPFailed(message: msg)))
36
+ return
37
+ }
38
+
39
+ downloadFile(url: url) { result in
40
+ switch result {
41
+ case .success(let cachedFile):
42
+ if fileExtension == "zip" {
43
+ handleZipFile(cachedFile: cachedFile, dbUrl: dbUrl, completion: completion)
44
+ } else {
45
+ handleNonZipFile(cachedFile: cachedFile, dbUrl: dbUrl, completion: completion)
31
46
  }
47
+ case .failure:
48
+ let msg = "Failed to download file: "
49
+ completion(.failure(UtilsDownloadError.downloadFromHTTPFailed(message: msg)))
32
50
  }
33
- // compute a path to this Url in the cache
34
- let cacheURL = UtilsFile.getTmpURL()
35
- let tmp = cacheURL.lastPathComponent
36
- let fileCacheURL = FileManager.default.temporaryDirectory
37
- .appendingPathComponent(
38
- dbName,
39
- isDirectory: false
40
- )
41
-
42
- let task = URLSession.shared
43
- .downloadTask(with: mUrl) {(tempURL, response, error) in
44
- // Early exit on error
45
- guard let tempURL = tempURL else {
46
- let msg = "\(String(describing: error?.localizedDescription))"
47
- print("\(msg)")
48
- completion(.failure(UtilsDownloadError.downloadFromHTTPFailed))
51
+ }
52
+ }
53
+
54
+ class func handleZipFile(cachedFile: URL, dbUrl: URL, completion: @escaping (Result<Bool, UtilsDownloadError>) -> Void) {
55
+ extractDBFiles(from: cachedFile) { dbFiles, error in
56
+ if let error = error {
57
+ let msg = "\(error.localizedDescription)"
58
+ completion(.failure(UtilsDownloadError.downloadFromHTTPFailed(message: msg)))
59
+ } else {
60
+ for file in dbFiles {
61
+ if let moveError = moveAndRenameFile(from: file, to: dbUrl) {
62
+ let msg = "Failed to move file: \(moveError.localizedDescription)"
63
+ completion(.failure(UtilsDownloadError.downloadFromHTTPFailed(message: msg)))
49
64
  return
50
65
  }
51
- if let httpResponse = response as? HTTPURLResponse {
52
- switch httpResponse.statusCode {
53
- case 200:
54
- do {
55
- // Remove any existing document at file
56
- if FileManager.default.fileExists(
57
- atPath: fileCacheURL.path) {
58
- try FileManager.default.removeItem(
59
- atPath: fileCacheURL.path)
60
- }
61
-
62
- // Copy the tempURL to file
63
- try FileManager.default.copyItem(
64
- at: tempURL,
65
- to: fileCacheURL
66
- )
67
- // Delete the tempUrl file
68
- try FileManager.default.removeItem(at: tempURL)
69
- let dbURL = try UtilsFile.getDatabaseLocationURL(
70
- databaseLocation: databaseLocation)
71
- if isZip {
72
- // get the zip files
73
- let zipList: [String] = try UtilsFile
74
- .getFileList(path: cacheURL.path,
75
- ext: ".zip")
76
- // loop through the database files
77
- for zip in zipList {
78
- _ = try UtilsFile.unzipToDatabase(
79
- fromURL: cacheURL,
80
- databaseLocation: tmp,
81
- zip: zip,
82
- overwrite: true)
83
- }
84
- // Delete the zip file
85
- try FileManager.default.removeItem(
86
- at: fileCacheURL)
87
-
88
- }
89
- try UtilsFile.moveAllDBSQLite(
90
- fromURL: cacheURL,
91
- dirUrl: dbURL)
92
- completion(.success(true))
93
- return
94
- }
95
-
96
- // Handle potential file system errors
97
- catch let error {
98
- let msg = "\(error.localizedDescription)"
99
- print("\(msg)")
100
- completion(.failure(UtilsDownloadError.downloadFromHTTPFailed))
101
- return
102
- }
103
- default:
104
- let msg = "Download: GET resquest not successful. http status code \(httpResponse.statusCode)"
105
- print("\(msg)")
106
- completion(.failure(UtilsDownloadError.downloadFromHTTPFailed))
107
- return
108
- }
109
- } else {
110
- let msg = "Download: not a valid http response"
111
- print("\(msg)")
112
- completion(.failure(UtilsDownloadError.downloadFromHTTPFailed))
66
+ }
67
+ completion(.success(true))
68
+ }
69
+ }
70
+ }
71
+
72
+ class func handleNonZipFile(cachedFile: URL, dbUrl: URL, completion: @escaping (Result<Bool, UtilsDownloadError>) -> Void) {
73
+ if let moveError = moveAndRenameFile(from: cachedFile, to: dbUrl) {
74
+ let msg = "Failed to move file: \(moveError.localizedDescription)"
75
+ completion(.failure(UtilsDownloadError.downloadFromHTTPFailed(message: msg)))
76
+ } else {
77
+ completion(.success(true))
78
+ }
79
+ }
80
+
81
+ class func getFileDetails(url: String) -> (filename: String, extension: String?)? {
82
+ guard let fileURL = URL(string: url) else { return nil }
83
+ let filename = fileURL.lastPathComponent
84
+ let ext = fileURL.pathExtension
85
+ return (filename, ext.isEmpty ? nil : ext)
86
+ }
87
+
88
+ class func downloadFile(url: String, completion: @escaping (Result<URL, Error>) -> Void) {
89
+ guard let fileURL = URL(string: url) else { return }
90
+
91
+ URLSession.shared.downloadTask(with: fileURL) { (location, _, error) in
92
+ if let location = location {
93
+ let tmpURL = UtilsFile.getTmpURL()
94
+ let suggestedFilename = fileURL.lastPathComponent
95
+
96
+ // Construct the destination URL
97
+ let destinationURL = tmpURL.appendingPathComponent(suggestedFilename)
98
+
99
+ // Check if the destination file already exists
100
+ if FileManager.default.fileExists(atPath: destinationURL.path) {
101
+ // Remove the existing file
102
+ do {
103
+ try FileManager.default.removeItem(at: destinationURL)
104
+ } catch {
105
+ completion(.failure(error))
113
106
  return
114
107
  }
115
108
  }
116
- // Start the download
117
- task.resume()
109
+
110
+ do {
111
+ try FileManager.default.moveItem(at: location, to: destinationURL)
112
+ completion(.success(destinationURL))
113
+ } catch {
114
+ print("Moving file \(error.localizedDescription)")
115
+ completion(.failure(error))
116
+ }
117
+ } else if let error = error {
118
+ print("Location file \(error.localizedDescription)")
119
+ completion(.failure(error))
120
+ }
121
+
122
+ }.resume()
123
+ }
124
+
125
+ class func moveAndRenameFile(from sourceURL: URL, to dbURL: URL) -> Error? {
126
+ let fileManager = FileManager.default
127
+
128
+ do {
129
+ let destinationURL = dbURL.appendingPathComponent(sourceURL.lastPathComponent).absoluteURL
130
+
131
+ // Ensure the destination directory exists
132
+ try fileManager.createDirectory(at: dbURL, withIntermediateDirectories: true, attributes: nil)
133
+ // Check if the destination file already exists and delete it if necessary
134
+ if fileManager.fileExists(atPath: destinationURL.path) {
135
+ try fileManager.removeItem(at: destinationURL)
136
+ }
137
+
138
+ // Move the file to the destination URL
139
+ try fileManager.moveItem(at: sourceURL, to: destinationURL)
140
+
141
+ // Rename the file if needed
142
+ let lastPathComponent = destinationURL.lastPathComponent
143
+ if lastPathComponent.hasSuffix(".db") && !lastPathComponent.contains("SQLite") {
144
+ let newLastPathComponent = lastPathComponent.replacingOccurrences(of: ".db", with: "SQLite.db")
145
+ let newDestinationURL = dbURL.appendingPathComponent(newLastPathComponent)
146
+ // Check if the destination file already exists and delete it if necessary
147
+ if fileManager.fileExists(atPath: newDestinationURL.path) {
148
+ try fileManager.removeItem(at: newDestinationURL)
149
+ }
150
+
151
+ try fileManager.moveItem(at: destinationURL, to: newDestinationURL)
152
+ }
153
+
154
+ return nil
155
+ } catch {
156
+ return error
118
157
  }
119
158
  }
120
- // swiftlint:enable cyclomatic_complexity
121
- // swiftlint:enable function_body_length
159
+
160
+ class func extractDBFiles(from zipFile: URL, completion: @escaping ([URL], Error?) -> Void) {
161
+ DispatchQueue.global().async(execute: {
162
+ var dbFiles: [URL] = []
163
+
164
+ do {
165
+ let destinationURL = zipFile.deletingLastPathComponent()
166
+
167
+ guard let archive = Archive(url: zipFile, accessMode: .read) else {
168
+ let msg = "Failed in reading Archive"
169
+ completion([], UtilsDownloadError.invalidArchive(message: msg))
170
+ return
171
+ }
172
+
173
+ for entry in archive where entry.type == .file {
174
+ let fileURL = destinationURL.appendingPathComponent(entry.path)
175
+ let fileExtension = URL(fileURLWithPath: entry.path).pathExtension
176
+
177
+ if fileExtension == "db" {
178
+ _ = try archive.extract(entry, to: fileURL)
179
+ dbFiles.append(fileURL)
180
+ }
181
+ }
182
+ // Delete the zip file
183
+ try FileManager.default.removeItem(at: zipFile)
184
+
185
+ completion(dbFiles, nil)
186
+ } catch {
187
+ completion([], error)
188
+ }
189
+ })
190
+
191
+ }
122
192
  }
@@ -569,9 +569,9 @@ class UtilsJson {
569
569
  let iterations: UInt32 = 10000
570
570
  var derivedKeyData = Data(count: keyLength)
571
571
  let derivedCount = derivedKeyData.count
572
- let derivationStatus = derivedKeyData.withUnsafeMutableBytes { derivedKeyUnsafeMutableRawBufferPointer in
573
- passphraseData.withUnsafeBytes { passphraseUnsafeRawBufferPointer in
574
- salt.withUnsafeBytes { saltUnsafeRawBufferPointer in
572
+ let derivationStatus = derivedKeyData.withUnsafeMutableBytes({ derivedKeyUnsafeMutableRawBufferPointer in
573
+ passphraseData.withUnsafeBytes({ passphraseUnsafeRawBufferPointer in
574
+ salt.withUnsafeBytes({ saltUnsafeRawBufferPointer in
575
575
  CCKeyDerivationPBKDF(
576
576
  CCPBKDFAlgorithm(kCCPBKDF2),
577
577
  passphraseUnsafeRawBufferPointer.baseAddress,
@@ -583,9 +583,9 @@ class UtilsJson {
583
583
  derivedKeyUnsafeMutableRawBufferPointer.baseAddress,
584
584
  derivedCount
585
585
  )
586
- }
587
- }
588
- }
586
+ })
587
+ })
588
+ })
589
589
  return derivationStatus == kCCSuccess ? derivedKeyData : nil
590
590
  }
591
591
  }
@@ -468,7 +468,7 @@ class UtilsSQLCipher {
468
468
  retMode = "wA\(retMode)"
469
469
  }
470
470
  }
471
- if (retMode == "no" || retMode.prefix(2) == "wA") {
471
+ if retMode == "no" || retMode.prefix(2) == "wA" {
472
472
  let stmtNames = UtilsSQLStatement
473
473
  .getStmtAndRetColNames(sqlStmt: sqlStmt,
474
474
  retMode: retMode)
@@ -845,7 +845,7 @@ class UtilsSQLCipher {
845
845
  }
846
846
 
847
847
  // MARK: - dbLastId
848
-
848
+
849
849
  class func dbLastId(mDB: OpaquePointer?) -> Int64 {
850
850
  return Int64(sqlite3_last_insert_rowid(mDB))
851
851
  }
@@ -1015,12 +1015,12 @@ class UtilsSQLCipher {
1015
1015
  var mRespSet = respSet
1016
1016
  if !retResponse.isEmpty {
1017
1017
  let keysInArray1 = ["ios_columns"]
1018
- mRespSet = mRespSet.filter { dict2 in
1018
+ mRespSet = mRespSet.filter({ dict2 in
1019
1019
  guard let dict2Key = dict2.keys.first else {
1020
1020
  return true // Keep dictionaries without any keys
1021
1021
  }
1022
1022
  return !keysInArray1.contains(dict2Key)
1023
- }
1023
+ })
1024
1024
  }
1025
1025
  retResponse.append(contentsOf: mRespSet)
1026
1026