Swift -Firebase 可以同时发布和观察

Posted

技术标签:

【中文标题】Swift -Firebase 可以同时发布和观察【英文标题】:Swift -Firebase is it possible to Post and Observe at same time 【发布时间】:2020-03-12 12:08:33 【问题描述】:

当我想observe 我跑:

let likesRef = Database.database().reference().child("likes").child(postId)
likesRef.observeSingleEvent(of: .value, with: (snapshot) in

    let likes = snapshot.childrenCount

    let totalLikes = Int(likes)

    // display totalLikes
)

当我想发帖时,我会运行:

let dict = [uid: 1]
let likesRef = Database.database().reference().child("likes").child(postId)
likesRef.updateChildValues(dict)  [weak self](error, ref) in
    if let error = error  return 
    // if no error then update the ref 
)

当发布 completionBlock 时有 2 个值:

withCompletionBlock: (Error?, DatabaseReference)

有没有办法让我也可以observe 使用第二个completionBlock 值:DatabaseReference

例如,一旦用户喜欢某样东西,我想更新 ref 并同时显示新的点赞数,而无需运行 observeSingleEvent

let dict = [uid: 1]
let likesRef = Database.database().reference().child("likes").child(postId)
likesRef.updateChildValues(dict)  (error, ref) in

    if let error = error  return 

    // *** somehow get the total amount of likes from the ref *** 

    // If possible I want to avoid this code below
    likesRef.observeSingleEvent(of: .value, with: (snapshot) in

        let likes = snapshot.childrenCount

        let totalLikes = Int(likes)

        // display totalLikes
     )
)

【问题讨论】:

有一个公认的答案,但问题就在这里。您实际上要做的是跟踪节点中的子节点数量。照原样,这是通过读取整个节点(可能是数千张选票)并使用 .childrenCount 来完成的。这将导致读取大量不必要的数据并推高成本。更好的解决方案是使用 .childChanged 或 .value(但不是两者)观察者创建一个单独的 child_count 节点。当投票添加到点赞节点时,将子 count_node 增加 1。每当更新时,您的应用都会收到通知,您可以更新您的 UI。 @Jay 观察 likes 节点和likesCount 节点有什么区别。为什么在两种情况下他们仍然有相同数量的孩子时,一个比另一个更好 @Jay 哦,我现在明白了,likeCount 节点只有 1 个子节点,即喜欢的总数。每次添加一个like 时,count_node 只会加1,减去它时会减1。有趣的。似乎 runTransactionBlock 对于 likesCount 节点会更好 是的!完全正确。这是Transaction Blocks 的完美用例,因为它是处理可能被并发修改(例如增量计数器)破坏的数据时的解决方案。看来您在这里走在正确的轨道上,因此请编写一些代码,如果您遇到困难,请告诉我,如果需要,我可以制作一个示例。 @Jay 我会在大约一个小时左右添加一些内容。谢谢:) 【参考方案1】:

实际上不可能同时获得两个事件。一旦您喜欢您的数据到达 firebase 数据库,那么您将在 App 端获得 Value、Change、ChieldAdded 事件。这将需要 1-2 秒或更短的时间,具体取决于 Internet。所以 Post 会先调用,然后观察者会调用。

您可以通过两种方式获得观察者:

第 1 步:添加子更改侦听器

let likesRef = Database.database().reference().child("likes").child(postId)
likesRef.observe(.childChanged, with: 
        (snapshot) in

    let likes = snapshot.childrenCount

    let totalLikes = Double(Int(likes))

    // display likes
 )

子变化监听器监听快照上的变化

第 2 步:放置值监听器

let likesRef = Database.database().reference().child("likes").child(postId)
likesRef.observe(.value, with: 
        (snapshot) in

    let likes = snapshot.childrenCount

    let totalLikes = Double(Int(likes))

    // display likes
 )

值监听器每次都会监听

我希望这会有所帮助...

【讨论】:

感谢您的回答。我实际上是想看看是否有一种方法可以在没有任何观察的情况下专门从该 ref 值中获取它。这是正确的方法,但这不是我想要的答案。给我几个小时,如果没有人发布任何关于我特别想做的事情,我会支持你的答案。谢谢:) @LanceSamaria 实际上不可能同时获得两个事件。一旦您喜欢您的数据到达 firebase 数据库,那么您将在 App 端获得 Value、Change、ChieldAdded 事件。这将需要 1-2 秒或更短的时间,具体取决于 Internet。所以 Post 会先调用,然后观察者会调用。 @LanceSamaria 我知道有一个可接受的答案,但我不确定它是否适合这个用例。此外,不需要添加 .childChanged 和 .value 事件侦听器,并且会在子项更改时调用both(增加您的成本)。此外,每次 any 子节点发生任何类型的更改时,答案中的代码都会加载 整个帖子 - 这是很多不必要的数据。 @Jay 我假设她/他说要使用 or 。我不会同时使用两者。我不知道它会在任何时候发生任何变化时加载。现在我看它是有道理的。您可以添加更新的答案吗?我会把它换成你的。除非有更好的答案,否则我无法撤消已接受的答案(为时已晚)。 @SilverskyTechnology 阅读了 Jay 制作的 cmets。他说你的回答将导致巨大的成本【参考方案2】:

这里有一些非常简短的代码来完成添加喜欢帖子的用户 uid 并通过 runTransactionBlock 递增计数器的任务。请记住,有 100 种方法可以做到这一点。

建议的结构

all_posts
   post_0
      like_count: 2
      post: "Hello, World"

likes
   post_0
      uid_0: true
      uid_1: true

基于问题和讨论,我将帖子和点赞分成不同的节点。原因是当帖子被加载时,它们可能会显示在一个列表中,此时真正需要的是帖子主题和喜欢的总数,所以我将“like_count”作为帖子节点的一部分。

如果我们在帖子节点本身中添加实际的点赞数,它可能是 10,000 或 10,000,000,并且在加载帖子时没有理由加载所有这些数据。拥有这么多点赞也可能使设备不堪重负。

在 likes 节点中,每个帖子键都引用 all_posts 节点中的帖子。孩子是喜欢帖子的用户的 uid,值是布尔值“真”作为占位符。没有值的节点不能存在,因此“true”将其保留在适当的位置并且是少量数据。

然后是代码——

func updatePostWithLikesViaTransaction() 
    let postsRef = self.ref.child("all_posts") //self.ref points to my Firebase
    let thisPostRef = postsRef.child("post_0")

    thisPostRef.runTransactionBlock ( (currentData: MutableData) -> TransactionResult in
        if var data = currentData.value as? [String: Any] 
            var count = data["like_count"] as? Int ?? 0
            count += 1
            data["like_count"] = count
            currentData.value = data
        

      return TransactionResult.success(withValue: currentData)
    )  (error, committed, snapshot) in
        if let error = error 
            print(error.localizedDescription)
            return
        
        print("successfully incremented counter")
        let likesRef = self.ref.child("likes")
        let likesPostRef = likesRef.child(thisPostRef.key!)
        likesPostRef.child("uid_2").setValue(true)
    

以上内容无论如何都不理想,但演示了过程(并与呈现的结构一起使用)。

首先,我们获得对我们要更新的帖子的引用,然后在事务块中读取 currentData 'like_count' 子节点,该子节点将根据结构中的值返回 2。增量为 3,在 currentData 中更新,然后通过 TransactionResult 更新。

runTransactionBlock 也有一个可选的完成回调,我们使用它来在成功后使用用户 uid 和 true 更新点赞中的帖子。

运行这个的最终结果是计数器增加到 3 并且 'uid_2: true' 被添加到 'likes/post_0' 节点

【讨论】:

以上是关于Swift -Firebase 可以同时发布和观察的主要内容,如果未能解决你的问题,请参考以下文章

在 Swift 3 中添加观察者和选择器

如何通过另一个可观察对象观察已发布的属性 - Swift Combine

Swift:计算属性的属性观察者

Swift Firestore 来自查询的自定义对象,同时使用 snapshotListener 监听实时更新

swift学习第十四天:属性监听器

Swift中的观察者模式