如何检测firebase观察childAdded在达到queryLimit时停止调用?

Posted

技术标签:

【中文标题】如何检测firebase观察childAdded在达到queryLimit时停止调用?【英文标题】:How can I detect firebase observe childAdded is stop calling when reach queryLimit? 【发布时间】:2017-09-18 08:59:32 【问题描述】:

现在,我对使用 childAdded 数据事件类型观察的 firebase 感到非常困惑。我之所以使用 childAdded 来观察我的 firebase,是因为我想让我的列表页面 动态 firebase 是否有新数据插入。

我的问题是如何知道当达到 queryLimit 时观察是否停止调用?因为我有一个指示器,我想在达到 queryLimit 时将其关闭。

我的firebase结构如下:

root 
  talkID1(id by auto create) 
    attribute1 ...
    attribute2 ...
    attribute3 ...
    time
  

  talkID2(id by auto create)
    ...
    time
  

  ... // many talk ID which auto create by firebase

据我所知,如果使用 childAdd 进行观察,数据将一个孩子一个孩子地在回调中传递数据。因此,如果我在 firebase 中有 N 个数据,我认为它会调用 N,对吗?

下面是我的完成处理程序:

func receiveIfExist(completion: @escaping (_ data: (My data type) -> Void) 
    let requestWithQuery = Database.database.reference().child("root").queryOrdered(byChild: "time")

    requestWithQuery.queryLimited(toLast: 5).observe(.childAdded, with:  (snapshot) in
        guard let value = snapshot.value as? [String: Any] else  return 

        self.getSingleTalkInfo(key: snapshot.key, value: value, completion:  (data) in
            completion(data)
        )
    )

我在 viewDidLoad() 中调用 receiveIfExist 这个函数。

override func viewDidLoad() 
    super.viewDidLoad()

    self.myIndicator.startAnimating() // start running indicator
    self.receiveIfExist  (data) in

        self.handleTalk(with: data) // do something with request data
        self.myIndicator.stopAnimating() // WEIRD!!!! Please look comments below

        /* 
           I think it can not be added here because this completion will call N<=5 times just what I said before. 
           I think it should detect what my queryLimit data is and check the request data is this queryLimit data or not.
           If yes then stop indicator animating, if not then keep waiting the queryLimit reach.
        */
     

如何检测观察是否达到 queryLimit?

如果我能检测到,那么我可以在指示灯到达时关闭它。

谢谢!

【问题讨论】:

【参考方案1】:

queryLimited(toLast: 5)

意思是(用更简单的话来说)请获取最后 5 个值(顺序由查询的前一部分决定)

1.现在,由于您是按 times 对数据进行排序,因此将检索最后 5 次的值,因此您的观察者将被触发 5 次

2.请注意,如果您的记录少于 5 条,比如 2 条记录,那么它只会触发两次,因为最大限制是 5,而不是最小限制

3. 另一点是,如果添加了一个新孩子,并且当您根据时间再次对项目进行排序并且新孩子是最后 5 个项目之一,那么这个观察者将是再次触发。

所以要获得查询限制,您可以像这样在代码中进行一些更改:

func receiveIfExist(completion: @escaping (data: YourType, limitCount: Int) -> Void) 
    let requestWithQuery = Database.database.reference().child("root").queryOrdered(byChild: "time")

    requestWithQuery.queryLimited(toLast: 5).observe(.childAdded, with:  (snapshot) in
        guard let value = snapshot.value as? [String: Any] else  return 

        self.getSingleTalkInfo(key: snapshot.key, value: value, completion:  (data) in
            self.index = self.index + 1
            completion(data, self.index)
        )
    )

然后使用上面的函数如下:

var index = 0
override func viewDidLoad() 
    super.viewDidLoad()

    self.myIndicator.startAnimating() // start running indicator
    self.receiveIfExist  (data, limitCount) in

        self.handleTalk(with: data) // do something with request data
        if limitCount == 5 
             self.myIndicator.stopAnimating()
        


     

更新:

由于 Kevin 提出了非常好的观点,如果我们说只有两条记录并且索引永远不会等于 5 并且myIndicator 不会停止动画,那么上述解决方案将失败,

我想到的一个解决方案是:

首先,我们使用observeSingleEvent 获取孩子数:

func getChildrenCount(completion: @escaping (_ childrenCount: Int) -> Void)
    Database.database.reference().child("root").observeSingleEvent(of:.value with:  (snapshot) in

                   completion(snapshot.children.count)

    
  

然后我们应用查询来获取最后 5 个项目:

func receiveIfExist(completion: @escaping (data: YourType, limitCount: Int) -> Void) 
        let requestWithQuery = Database.database.reference().child("root").queryOrdered(byChild: "time")

        requestWithQuery.queryLimited(toLast: queryLimit).observe(.childAdded, with:  (snapshot) in
            guard let value = snapshot.value as? [String: Any] else  return 

            self.getSingleTalkInfo(key: snapshot.key, value: value, completion:  (data) in
                self.index = self.index + 1
                completion(data, self.index)
            )
        )
    

然后在您的代码中使用此计数,如下所示:

var index = 0
var childrenCount = 0
var queryLimit = 5
override func viewDidLoad() 
    super.viewDidLoad()

    self.myIndicator.startAnimating() // start running indicator
    self.getChildrenCount (snapChildrenCount) in
         self.childrenCount = snapChildrenCount

         self.receiveIfExist  (data, limitCount) in

        self.handleTalk(with: data) // do something with request data
        if (self.childrenCount < self.queryLimit && limitCount == self.childrenCount) || limitCount == self.queryLimit  
             DispatchQueue.main.async 
                   self.myIndicator.stopAnimating()
             

        


     

    


【讨论】:

感谢您的回复。但是我有一个问题,就是您在第二点中所说的“如果我的根目录中只有两条记录,那么它只会被触发两次,对吗?”如果是这样,“self.index = self.index + 1”这一行只会被调用两次,导致limitCount的总和只有2? 它可能会导致limitCount只有2并且可能不会停止myIndi​​cator,对吧? @Kevin 是的,您是对的,我认为我们无法以任何其他方式真正知道记录数,所以我认为上述解决方案部分正确,我没有尝试过,请尝试并分享输出,你学到了什么,谢谢 其实,我有个想法是我可以使用 .queryLimit(toLast: 1) 并与这个 queryLimit(toLast: 5) 进行比较。因为查询会按时间返回,所以 queryLimit(toLast: 5) 的最后一个数据将与 queryLimit(toLast: 1) 相同。所以我可以检查firebase自动创建的这两个数据的keyID,如果相同则可以停止指标,否则继续等待下一个数据等等。 @Kevin 好主意,你是说你做了两个查询,对吧?【参考方案2】:
func receiveIfExist(limitCount: UInt, completion: @escaping (data: MyDataType) -> Void) 
    let requestWithQuery = Database.database.reference().child("root").queryOrdered(byChild: "time")

    requestWithQuery.queryLimited(toLast: limitCount).observe(.childAdded, with:  (snapshot) in
        guard let value = snapshot.value as? [String: Any] else  return 

        self.getSingleTalkInfo(key: snapshot.key, value: value, completion:  (data) in
            completion(data)
        )
    )

我也做这个功能只观察单个孩子的价值

let requestTalks = Database.database.reference().child("root")    

func getSingleTalk(by key: String = "", at limitType: TalkLimitType, completion: @escaping (_ eachData: MyDataType) -> Void) 

    var requestSingleTalk: DatabaseQuery 
        switch limitType 
        case .first :
            return self.requestTalks.queryOrdered(byChild: "time").queryLimited(toFirst: 1)

        case .last :
            return self.requestTalks.queryOrdered(byChild: "time").queryLimited(toLast: 1)

        case .specificKey :
            return self.requestTalks.child(key)
        
    

    requestSingleTalk.observeSingleEvent(of: .value, with:  (snapshot) in

        if limitType == .specificKey 
            guard let value = snapshot.value as? [String: Any] else  return 

            self.getSingleTalkInfo(key: snapshot.key, value: value, completion:  (data) in
                completion(data)
            )
         else 
            guard let snapshotValue = snapshot.value as? NSDictionary,
                  let eachTalkKey = snapshotValue.allKeys[0] as? String,
                  let eachTalkValue = snapshotValue.value(forKey: eachTalkKey) as? [String: Any] else  return 

            self.getSingleTalkInfo(key: eachTalkKey, value: eachTalkValue, completion:  (data) in
                completion(data)
            )
        
    )

因此,我可以在 viewDidLoad() 中执行类似的操作

override func viewDidLoad() 
    super.viewDidLoad()

    self.myIndicator.startAnimating()
    self.receiveIfExist(limitCount: 5)  (eachData) in
        self.handleTalk(with: eachData)
        self.getSingleTalk(at: .last, completion:  (lastData) in
            if eachData.keyID == lastData.keyID
                DispatchQueue.main.async 
                    self.myIndicator.stopAnimating()
                
            
        )
     

【讨论】:

这对你有用吗,你测试过吗?如果是,那很好,我只想提出一件事,每次 UI 更改都应该在主队列上完成,所以只需在 @ 中添加 self.myIndicator.stopAnimating() 987654325@这样的块:DispatchQueue.main.async self.myIndicator.stopAnimating() 哦,是的,很抱歉我忘记了。

以上是关于如何检测firebase观察childAdded在达到queryLimit时停止调用?的主要内容,如果未能解决你的问题,请参考以下文章

Swift Firebase观察.childAdded对现有项目而不是新项目的反应

Firebase ChildAdded 每次在我的 iOS 应用程序中触发

在 Firebase iOS SDK 中,每次删除孩子时都会触发 .childAdded。我怎样才能阻止这个?

火力基地。有没有办法忽略在 iOS 上调用 childAdded?

Swift -Firebase 如何知道 .childAdded 是不是为空?

Firebase + Android - 通知 ChildAdded - 如何只获得新的孩子?