You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 4 Firebase下仅允许用户删除自身评论的代码异常排查

问题描述

我刚实现了用户对他人帖子评论并删除自己评论的功能,但最初的代码允许所有人删除任意评论。之后我尝试修改代码限制仅能删除自己的评论,但现在遇到了两个问题:

  1. 只有UITableView第一行的评论能正常触发删除验证逻辑
  2. 删除其他行时,会在let usersArray = self.uidArray[indexPath.row] as! [String: AnyObject]处触发索引越界错误

数据库结构正常,但我无法定位问题根源,希望得到技术帮助。

初始代码(存在任意删除问题)

func getKeysValue() {
    // let uid = Auth.auth().currentUser?.uid
    Database.database().reference().child("posts").child(postsKey).child("comments").observe( .value) { (snapshot) in
        for child in snapshot.children {
            let snap = child as! DataSnapshot
            let commentKey = snap.key
            self.keyArray.insert(commentKey, at: 0)
            // print(self.keyArray)
            // print("Here is the specific Key\(self.commentKey)")
        }
    }
}

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        getKeysValue()
        let when = DispatchTime.now() + 1
        DispatchQueue.main.asyncAfter(deadline: when, execute: {
            Database.database().reference().child("posts").child(postsKey).child("comments").child(self.keyArray[indexPath.row]).removeValue()
            self.commentsTableView.reloadData()
        })
    }
}

修改后的代码(出现索引越界问题)

func getKeysValue() {
    // let uid = Auth.auth().currentUser?.uid
    Database.database().reference().child("posts").child(postsKey).child("comments").observe( .value) { (snapshot) in
        for child in snapshot.children {
            let snap = child as! DataSnapshot
            let commentKey = snap.key
            self.keyArray.insert(commentKey, at: 0)
            // print(self.keyArray)
            // print("Here is the specific Key\(self.commentKey)")
        }
    }
}

var uid = Auth.auth().currentUser?.uid
var userID = String()
var idUser = String()
var userUID = String()

func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true
}

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        getKeysValue()
        let when = DispatchTime.now() + 1
        DispatchQueue.main.asyncAfter(deadline: when, execute: {
            self.userID = self.keyArray[indexPath.row]
            self.idUser = self.userID
            print(" Here is the indexPath key: \(self.idUser)")
            Database.database().reference().child("posts").child(postsKey).child("comments").child(self.idUser).observeSingleEvent(of: .value, with: { (snapshot) in
                if let postsDictionary = snapshot.value as? [String: AnyObject] {
                    self.uidArray.insert(postsDictionary as NSDictionary, at: 0)
                    let usersArray = self.uidArray[indexPath.row] as! [String: AnyObject]
                    let usersKey = usersArray["uid"] as? String
                    self.userUID = usersKey!
                    print("Here is the Comment:\(self.uidArray)")
                    print("And here is the UID::: \(self.userUID)")
                    if self.uid != self.userUID {
                        let logInAlert = UIAlertController(title: "Failed to delete", message: "You can only delete your own comment(s)", preferredStyle: .actionSheet)
                        logInAlert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                        self.present(logInAlert, animated: true, completion: nil)
                    }else {
                        let commentSuccess = self.storyboard?.instantiateViewController(withIdentifier: "CommentReloadVC")
                        self.present(commentSuccess!, animated: false, completion: nil)
                        Database.database().reference().child("posts").child(postsKey).child("comments").child(self.keyArray[indexPath.row]).removeValue()
                        self.commentsTableView.reloadData()
                    }
                }
            })
        })
    }
}

问题根源分析

你的代码出现索引越界和仅第一行有效的问题,主要有以下几个核心原因:

  1. 数组插入逻辑错误:你每次获取评论数据时都用insert(_:at:0)把元素插在头部,导致uidArray和keyArray的顺序与UITableView行索引完全不匹配——比如第二行评论对应的数组索引其实是1,但你插入的新元素都挤在0位,用indexPath.row取值时自然只有第一行能匹配,其他行直接越界。
  2. 异步操作与数组管理混乱:
    • getKeysValue()是异步的,用DispatchQueue.main.asyncAfter延迟1秒等待数据的做法完全不可靠,而且每次触发删除都会重复调用它,导致keyArray不断重复添加元素,数组长度越来越大,索引彻底混乱。
    • 删除逻辑中每次获取UID都往uidArray插元素,却从不清空旧数据,数组越攒越长,索引完全对应不上。
  3. 冗余操作:删除成功后跳转到CommentReloadVC完全没必要,直接刷新当前TableView即可。

修正后的解决方案

我重构了你的代码,解决所有问题的同时优化了逻辑严谨性:

步骤1:统一数据模型与数组管理

先定义一个评论模型类(也可以用字典数组),把评论的key和uid绑定在一起,确保数据与TableView行索引一一对应:

// 评论模型,存储必要的删除验证信息
struct Comment {
    let key: String
    let uid: String
    // 可以添加其他字段,比如评论内容、时间戳等
}

var comments: [Comment] = [] // 替代原来的keyArray和uidArray
let currentUserUID = Auth.auth().currentUser?.uid ?? "" // 提前获取当前用户UID,避免重复调用

步骤2:重构评论数据获取逻辑

改成一次性获取所有评论并解析成Comment数组,确保数据顺序与TableView一致:

func fetchComments() {
    comments.removeAll() // 先清空旧数据,避免重复
    Database.database().reference().child("posts").child(postsKey).child("comments").observeSingleEvent(of: .value) { snapshot in
        for child in snapshot.children {
            guard let snap = child as? DataSnapshot,
                  let commentDict = snap.value as? [String: Any],
                  let commentUID = commentDict["uid"] as? String else {
                continue // 安全解包,跳过无效数据
            }
            let comment = Comment(key: snap.key, uid: commentUID)
            self.comments.append(comment)
        }
        // 如果你想让最新评论在顶部,反转数组即可
        self.comments.reverse()
        self.commentsTableView.reloadData()
    }
}

步骤3:优化TableView编辑与删除逻辑

在canEditRowAt中直接判断当前行是否属于当前用户,避免删除时再查数据库;删除时直接用评论的key操作数据库:

// 控制哪些行显示删除按钮——只有自己的评论才允许删除
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    let comment = comments[indexPath.row]
    return comment.uid == currentUserUID
}

// 处理删除操作
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        let commentToDelete = comments[indexPath.row]
        // 删除数据库中的评论
        Database.database().reference().child("posts").child(postsKey).child("comments").child(commentToDelete.key).removeValue { error, _ in
            if let error = error {
                // 处理删除失败的情况
                let alert = UIAlertController(title: "删除失败", message: error.localizedDescription, preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default))
                self.present(alert, animated: true)
            } else {
                // 删除本地数组元素并刷新TableView
                self.comments.remove(at: indexPath.row)
                self.commentsTableView.deleteRows(at: [indexPath], with: .automatic)
            }
        }
    }
}

步骤4:初始化加载评论

在viewDidLoad中调用fetchComments()加载初始数据:

override func viewDidLoad() {
    super.viewDidLoad()
    fetchComments()
    // 如果需要实时监听评论变化,可以把observeSingleEvent改成observe(.value),但要注意每次回调都清空数组
}

额外优化建议

  • 避免强制解包:代码里的!强制解包很容易导致崩溃,尽量用guard let或if let安全解包。
  • 数据库规则验证:除了客户端验证,一定要在Firebase数据库规则中添加权限校验,确保只有评论所有者才能删除评论,防止恶意用户绕过客户端逻辑。
  • 实时更新:如果需要实时同步评论变化,把observeSingleEvent改成observe(.value)即可,但要注意每次回调都先清空comments数组再重新加载。

内容的提问来源于stack exchange,提问作者Jiyar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:09:31