firestore 文档删除并因逻辑错误而崩溃,但在没有逻辑的情况下可以正常删除
firestore document deletes and crashes with an error in logic but deletes fine without logic
所以我的目标是在条件为假且没有错误的情况下删除一个 firestore 文档。起初,我有这个删除firestore文件的功能:
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { (deleted, view, completion) in
let alert = UIAlertController(title: "Delete Event", message: "Are you sure you want to delete this event?", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (cancel) in
self.dismiss(animated: true, completion: nil)
}
let deleteEvent = UIAlertAction(title: "Delete", style: .destructive) { (deletion) in
guard let user = Auth.auth().currentUser else { return }
let documentid = self.documentsID[indexPath.row].docID
// let eventName = self.events[indexPath.row].eventName
let deleteIndex = client.index(withName: IndexName(rawValue: user.uid))
deleteIndex.deleteObject(withID: ObjectID(rawValue: self.algoliaObjectID[indexPath.row].algoliaObjectID)) { (result) in
if case .success(let response) = result {
print("Algolia document successfully deleted: \(response.wrapped)")
}
}
self.db.document("school_users/\(user.uid)/events/\(documentid)").delete { (error) in
guard error == nil else {
print("There was an error deleting the document.")
return
}
print("Deleted")
}
self.events.remove(at: indexPath.row)
tableView.reloadData()
}
alert.addAction(cancelAction)
alert.addAction(deleteEvent)
self.present(alert, animated: true, completion: nil)
}
deleteAction.backgroundColor = UIColor.systemRed
let config = UISwipeActionsConfiguration(actions: [deleteAction])
config.performsFirstActionWithFullSwipe = false
return config
}
所以这会删除单元格和云 firestore 中的文档。现在我在我的应用程序中遇到了一个问题,如果学校用户想要删除他们创建的事件但一些学生用户已经购买了这个事件,这个事件将删除是的,但是如果购买该事件的学生用户试图查看他们购买的事件,值将为 nil
并且应用程序将崩溃,因为他们购买的事件的详细信息完全依赖于创建的事件(这不是我不会改变的概念,这只是应用程序的方式作品)。
已编辑代码 为了解决这个问题,我决定在删除过程中添加一些逻辑:
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { (deleted, view, completion) in
let alert = UIAlertController(title: "Delete Event", message: "Are you sure you want to delete this event?", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (cancel) in
self.dismiss(animated: true, completion: nil)
}
let deleteEvent = UIAlertAction(title: "Delete", style: .destructive) { (deletion) in
guard let user = Auth.auth().currentUser else { return }
let documentid = self.documentsID[indexPath.row].docID
let eventName = self.events[indexPath.row].eventName
let deleteIndex = client.index(withName: IndexName(rawValue: user.uid))
self.getTheSchoolsID { (id) in
guard let id = id else { return }
self.db.collection("student_users").whereField("school_id", isEqualTo: id).getDocuments { (querySnapshot, error) in
guard error == nil else {
print("Couldn't fetch the student users.")
return
}
for document in querySnapshot!.documents {
let userUUID = document.documentID
self.db.collection("student_users/\(userUUID)/events_bought").whereField("event_name", isEqualTo: eventName).getDocuments { (querySnapshotTwo, error) in
guard error == nil else {
print("Couldn't fetch if users are purchasing this event")
return
}
guard querySnapshotTwo?.isEmpty == true else {
self.showAlert(title: "Students Have Purchased This Event", message: "This event cannot be deleted until all students who have purchased this event have completely refunded their purchase of this event. Please be sure to make an announcement that this event will be deleted.")
return
}
}
}
deleteIndex.deleteObject(withID: ObjectID(rawValue: self.algoliaObjectID[indexPath.row].algoliaObjectID)) { (result) in
if case .success(let response) = result {
print("Algolia document successfully deleted: \(response.wrapped)")
}
}
self.db.document("school_users/\(user.uid)/events/\(documentid)").delete { (error) in
guard error == nil else {
print("There was an error deleting the document.")
return
}
print("Deleted")
}
self.events.remove(at: indexPath.row)
tableView.reloadData()
}
}
}
alert.addAction(cancelAction)
alert.addAction(deleteEvent)
self.present(alert, animated: true, completion: nil)
}
deleteAction.backgroundColor = UIColor.systemRed
let config = UISwipeActionsConfiguration(actions: [deleteAction])
config.performsFirstActionWithFullSwipe = false
return config
}
UPDATE 所以我完全退出循环,然后如果查询为空则继续删除过程。现在,我通过以学生用户的身份购买一个活动,然后尝试以学校用户的身份删除相同的活动来对此进行测试。出于某种原因,当我在第一个警报中按下删除操作时,事件先被删除,然后验证警报立即出现,但没有错误地崩溃。是的,崩溃消失了,这很好,但是我查询中的 return
方法实际上并没有 return 并中断删除方法,它删除然后显示错误,我没有得到.
有什么建议吗?
好的,我想我找到了问题所在。当您浏览代表就读特定学校的用户的每份文档时,您检查这些用户中的每一个 他们是否正在参加试图删除的活动。问题是,每次执行此检查以查看用户是否参加活动时,您还会执行以下操作之一(基于该用户的 querySnapshotTwo?.isEmpty
是否为真):
- 如果用户正在参加该活动,您将显示警告,说明无法删除该活动(因此,这种情况会发生的次数与参加该活动的用户一样多,而不仅仅是一次), 或:
- 如果用户不参加活动,它执行删除活动操作(因此,如果多个用户不参加活动,这也会发生多次)。
我不确定究竟是什么导致应用程序因 nil
值而在 self.events.remove(at: indexPath.row)
崩溃。但我会首先确保您 完全退出 如果您发现用户正在参加活动然后显示警报,则循环浏览所有用户文档。然后只有当全部用户文档,发现没有人参加活动时才执行删除操作。
响应更新
太棒了,你所做的绝对是一种进步。当你发现一个用户参加活动时(即,当 guard querySnapshotTwo?.isEmpty == true
评估为 false
时)它没有 return 退出删除方法的原因是因为你只是 return ing 闭包内。本质上,您只是 return 越界了:self.db.collection("student_users/\(userUUID)/events_bought").whereField("event_name", isEqualTo: eventName).getDocuments { (querySnapshotTwo, error)
然后您将继续 querySnapshot!.documents
.
中的下一个文档
所以即使学校的每个学生都参加了活动,你总是会完成 for 循环并继续删除活动!
它不起作用的另一个重要原因是因为您传递给 getDocuments() 调用的闭包是 运行 异步 。这意味着每次您进行调用时,它将在将来的某个随机时间安排闭包为 运行,然后立即从 getDocuments() 调用中 return 并执行下一次迭代for 循环(可能在闭包完成之前)。
要解决此问题,我相信您只需进行 2 处更改:
- 在
for document in querySnapshot!.documents
之前添加一个变量,用于跟踪是否找到参加活动的学生,如果找到学生,则将此变量更新为闭包内的true
参加活动(代替当前无效的 return 声明)。
- 您将需要使用 DispatchGroup 以便仅在异步检查每个学生后才执行删除操作。下面是关于如何使用 Dispatch Groups 的简短 tutorial。在你的情况下:
- 在for循环之前创建调度组(写
let group = DispatchGroup()
)
- 在for循环内的第一行调用
group.enter()
- 在 for 循环内的 getDocuments() 闭包内的最后一行调用
group.leave()
- 在 for 循环之后立即调用以下代码:
group.notify(queue: .main) {
// deletion code goes here
}
所以我的目标是在条件为假且没有错误的情况下删除一个 firestore 文档。起初,我有这个删除firestore文件的功能:
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { (deleted, view, completion) in
let alert = UIAlertController(title: "Delete Event", message: "Are you sure you want to delete this event?", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (cancel) in
self.dismiss(animated: true, completion: nil)
}
let deleteEvent = UIAlertAction(title: "Delete", style: .destructive) { (deletion) in
guard let user = Auth.auth().currentUser else { return }
let documentid = self.documentsID[indexPath.row].docID
// let eventName = self.events[indexPath.row].eventName
let deleteIndex = client.index(withName: IndexName(rawValue: user.uid))
deleteIndex.deleteObject(withID: ObjectID(rawValue: self.algoliaObjectID[indexPath.row].algoliaObjectID)) { (result) in
if case .success(let response) = result {
print("Algolia document successfully deleted: \(response.wrapped)")
}
}
self.db.document("school_users/\(user.uid)/events/\(documentid)").delete { (error) in
guard error == nil else {
print("There was an error deleting the document.")
return
}
print("Deleted")
}
self.events.remove(at: indexPath.row)
tableView.reloadData()
}
alert.addAction(cancelAction)
alert.addAction(deleteEvent)
self.present(alert, animated: true, completion: nil)
}
deleteAction.backgroundColor = UIColor.systemRed
let config = UISwipeActionsConfiguration(actions: [deleteAction])
config.performsFirstActionWithFullSwipe = false
return config
}
所以这会删除单元格和云 firestore 中的文档。现在我在我的应用程序中遇到了一个问题,如果学校用户想要删除他们创建的事件但一些学生用户已经购买了这个事件,这个事件将删除是的,但是如果购买该事件的学生用户试图查看他们购买的事件,值将为 nil
并且应用程序将崩溃,因为他们购买的事件的详细信息完全依赖于创建的事件(这不是我不会改变的概念,这只是应用程序的方式作品)。
已编辑代码 为了解决这个问题,我决定在删除过程中添加一些逻辑:
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { (deleted, view, completion) in
let alert = UIAlertController(title: "Delete Event", message: "Are you sure you want to delete this event?", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (cancel) in
self.dismiss(animated: true, completion: nil)
}
let deleteEvent = UIAlertAction(title: "Delete", style: .destructive) { (deletion) in
guard let user = Auth.auth().currentUser else { return }
let documentid = self.documentsID[indexPath.row].docID
let eventName = self.events[indexPath.row].eventName
let deleteIndex = client.index(withName: IndexName(rawValue: user.uid))
self.getTheSchoolsID { (id) in
guard let id = id else { return }
self.db.collection("student_users").whereField("school_id", isEqualTo: id).getDocuments { (querySnapshot, error) in
guard error == nil else {
print("Couldn't fetch the student users.")
return
}
for document in querySnapshot!.documents {
let userUUID = document.documentID
self.db.collection("student_users/\(userUUID)/events_bought").whereField("event_name", isEqualTo: eventName).getDocuments { (querySnapshotTwo, error) in
guard error == nil else {
print("Couldn't fetch if users are purchasing this event")
return
}
guard querySnapshotTwo?.isEmpty == true else {
self.showAlert(title: "Students Have Purchased This Event", message: "This event cannot be deleted until all students who have purchased this event have completely refunded their purchase of this event. Please be sure to make an announcement that this event will be deleted.")
return
}
}
}
deleteIndex.deleteObject(withID: ObjectID(rawValue: self.algoliaObjectID[indexPath.row].algoliaObjectID)) { (result) in
if case .success(let response) = result {
print("Algolia document successfully deleted: \(response.wrapped)")
}
}
self.db.document("school_users/\(user.uid)/events/\(documentid)").delete { (error) in
guard error == nil else {
print("There was an error deleting the document.")
return
}
print("Deleted")
}
self.events.remove(at: indexPath.row)
tableView.reloadData()
}
}
}
alert.addAction(cancelAction)
alert.addAction(deleteEvent)
self.present(alert, animated: true, completion: nil)
}
deleteAction.backgroundColor = UIColor.systemRed
let config = UISwipeActionsConfiguration(actions: [deleteAction])
config.performsFirstActionWithFullSwipe = false
return config
}
UPDATE 所以我完全退出循环,然后如果查询为空则继续删除过程。现在,我通过以学生用户的身份购买一个活动,然后尝试以学校用户的身份删除相同的活动来对此进行测试。出于某种原因,当我在第一个警报中按下删除操作时,事件先被删除,然后验证警报立即出现,但没有错误地崩溃。是的,崩溃消失了,这很好,但是我查询中的 return
方法实际上并没有 return 并中断删除方法,它删除然后显示错误,我没有得到.
有什么建议吗?
好的,我想我找到了问题所在。当您浏览代表就读特定学校的用户的每份文档时,您检查这些用户中的每一个 他们是否正在参加试图删除的活动。问题是,每次执行此检查以查看用户是否参加活动时,您还会执行以下操作之一(基于该用户的 querySnapshotTwo?.isEmpty
是否为真):
- 如果用户正在参加该活动,您将显示警告,说明无法删除该活动(因此,这种情况会发生的次数与参加该活动的用户一样多,而不仅仅是一次), 或:
- 如果用户不参加活动,它执行删除活动操作(因此,如果多个用户不参加活动,这也会发生多次)。
我不确定究竟是什么导致应用程序因 nil
值而在 self.events.remove(at: indexPath.row)
崩溃。但我会首先确保您 完全退出 如果您发现用户正在参加活动然后显示警报,则循环浏览所有用户文档。然后只有当全部用户文档,发现没有人参加活动时才执行删除操作。
响应更新
太棒了,你所做的绝对是一种进步。当你发现一个用户参加活动时(即,当 guard querySnapshotTwo?.isEmpty == true
评估为 false
时)它没有 return 退出删除方法的原因是因为你只是 return ing 闭包内。本质上,您只是 return 越界了:self.db.collection("student_users/\(userUUID)/events_bought").whereField("event_name", isEqualTo: eventName).getDocuments { (querySnapshotTwo, error)
然后您将继续 querySnapshot!.documents
.
所以即使学校的每个学生都参加了活动,你总是会完成 for 循环并继续删除活动!
它不起作用的另一个重要原因是因为您传递给 getDocuments() 调用的闭包是 运行 异步 。这意味着每次您进行调用时,它将在将来的某个随机时间安排闭包为 运行,然后立即从 getDocuments() 调用中 return 并执行下一次迭代for 循环(可能在闭包完成之前)。
要解决此问题,我相信您只需进行 2 处更改:
- 在
for document in querySnapshot!.documents
之前添加一个变量,用于跟踪是否找到参加活动的学生,如果找到学生,则将此变量更新为闭包内的true
参加活动(代替当前无效的 return 声明)。 - 您将需要使用 DispatchGroup 以便仅在异步检查每个学生后才执行删除操作。下面是关于如何使用 Dispatch Groups 的简短 tutorial。在你的情况下:
- 在for循环之前创建调度组(写
let group = DispatchGroup()
) - 在for循环内的第一行调用
group.enter()
- 在 for 循环内的 getDocuments() 闭包内的最后一行调用
group.leave()
- 在 for 循环之后立即调用以下代码:
- 在for循环之前创建调度组(写
group.notify(queue: .main) {
// deletion code goes here
}