【问题标题】:How to query thousands of values and only return ones in array - Firebase Swift如何查询数千个值并仅返回数组中的值 - Firebase Swift
【发布时间】:2018-06-10 21:07:28
【问题描述】:

在 firebase 实时数据库中,我有这样的数据:

"users" : {
"37KfZKDwrieIEKI9juAC4Xm8aPi1" : {
  "isConnected" : false,
  "isGuestUser" : false,
  "lastOnline" : 1510250272022,
  "profilePicture" : "5a039030515e78653148",
  "userID" : "37KfZKDwrieIEKI9juAC4Xm8aPi1",
  "username" : "adsfasdfasdf"
},
"4D1GNiRH5NeRxpmTNg6JhJ3iTck1" : {
  "isConnected" : false,
  "isGuestUser" : true,
  "lastOnline" : 1510077502788,
  "profilePicture" : "5a01f2648278b6652011",
  "userID" : "4D1GNiRH5NeRxpmTNg6JhJ3iTck1",
  "username" : "ihoho"
},
"5UA3INZ7i0dnNtgX0ai5ABhjxh43" : {
  "isConnected" : false,
  "isGuestUser" : true,
  "lastOnline" : 1512610102474,
  "profilePicture" : "5a14df775a34f2388873",
  "userID" : "5UA3INZ7i0dnNtgX0ai5ABhjxh43",
  "username" : "jlkjlkjlkj"
},...

我正在使用一个外部 API,它返回一个看起来像这样的 Json

"candidates" : [
    {
      "enrollment_timestamp" : "1510182689539",
      "subject_id" : "37KfZKDwrieIEKI9juAC4Xm8aPi1",
    },
    {
      "enrollment_timestamp" : "1513557650425",
      "subject_id" : "CKUVZ7XtY9VKJakn1lBV7MVW1702",
    },
    {
      "enrollment_timestamp" : "1507578748901",
      "subject_id" : "l7VDdtGFpMe8BRbrlCyAciTvONk1",
    },...

最终目标是从 json 中获取所有在线的外部 api 用户。这需要监听每个用户的“isConnected”端点,并确定它是真还是假。

现在使用 firebase 和我当前的数据结构这是不可能的,因为首先 firebase 不支持多个查询参数,所以我不能做 where userID = subjectID && where isConnected == true,其次,更重要的是,firebase 不允许我做相当于 WHERE IN,即将用户 ID 映射到客户端提供的潜在用户 ID 数组(仅供参考 subjectID === userID)

所以我所做的就是像这样重组我的数据;

"onlineUsers" : {
"Aze1x7jTZIbPyyPmlUYWVdEyAd73" : true,
"CQdmMxkmqBerRGOQpFblx7SO4D33" : true,
"EptMK62Kt1Sp1EIb5meuKHVpUqs1" : true,
"J2X65KauDlSvkN4Yp5V4IF0sTnx1" : true,
"KnYEqsekY9YXV3ayOx082xw8VQX2" : true,
"aGLrKH31YvRKrB8KYQmZ4sA122m1" : true,
"ab3JZyw9YMdpW3mXkZc2BjYkxej2" : true,
"gpQic1EzSnXL9x5DhaoXxcWrGF22" : true,
"qQaBPMaDOrXrWddsijfMJWusuGG3" : true,
"tDWEUoKS4mUdQ1bWeiLTlhwCSoD3" : true

},

现在,要获取外部 api json 中的所有在线用户,我所要做的就是查询 onlineUsers 并通过键检查条件参数。这样我就知道结果是否为空,用户不在线:

    static func queryDatabase(child: String, queryEqual: String, keyOf: Int, completionHandler: @escaping (_ return: AnyObject?, _ error: String?) -> Void){
    print("querying db with child ", child)

    let ref = databaseReference.child(child).queryOrderedByKey().queryEqual(toValue: queryEqual)

     ref.observe(.value, with:{ (snapshot: DataSnapshot) in
        print("subjectID: ", queryEqual, "at key ", keyOf)
        print("queryResult", snapshot)

        if let value =  (snapshot.value as? [String: AnyObject])?[queryEqual] {
            print("unwrapped snapshot dict value from key: ", value)

            completionHandler(value,  nil)

        }else{
            print("no value for key \(queryEqual) so setting return as nil")

            completionHandler(nil,  nil)

        }



    }){ (error) in
        print(error.localizedDescription)

        completionHandler(nil,  error.localizedDescription )


    }

}

通过循环外部 api json 调用这个函数很简单,每次迭代调用这个函数 ^^,类似于:

      for (key, valueSubjectID) in arrayOfOrderedMatches.enumerated(){
         //call function queryDatabase here

    queryDatabase(child: "onlineUsers", queryEqual: valueSubjectID, keyOf: key, completionHandler:{ (response, error) in


//it would return the user if they were online here


)}
    }

现在。这行得通。在浏览互联网并考虑所有可能性后,这是我能想到的最有效的方法。但是,现在遇到一个大问题:来自 api 的 json 可能有成千上万的用户。这意味着将连接数以千计的听众来检查每个单独的用户,看看他们是否在线。正如 Firebase 开发人员告诉我的那样,这并不理想。我不应该附上成千上万的听众。此外,由于某种原因,听众在 3500 左右停止添加,我猜是因为它出错了。如果查询返回 null (即脱机),我无法删除侦听器,因为缓存脱机持久性的工作方式,即使我可以,我也不认为这会解决问题。有什么办法可以解决这个问题吗?

【问题讨论】:

  • 信息量很大,但不清楚为什么要附加成千上万的听众。看来您有一个已连接用户的节点;您可以通过.value 在该节点上观察SingleEvent,将键放在一个数组中并遍历每个子节点并从外部源中提取您需要的任何数据吗?不需要听众。也许你可以缩短(见MCV)并澄清问题?
  • observeSingleEvent 太可怕了,永远不应该使用。它只监听缓存,然后分离自身,因此它永远不会获取最新的服务器值,这对于检测用户是否在线至关重要。这个问题中的所有信息对于理解问题都是必要的。此外,正如我所解释的,外部源返回一次性 json,这意味着我不能只查询我想要的数据。无论如何,由于 api 的性质,json 将包含数千个结果。 json 也告诉我需要查询哪些用户,而不是反过来。
  • 哇。哈哈。这确实是错误的信息,我不确定你从哪里得到的。 ObserveSingleEvent 专门用于读取一次值而不离开观察者。请参阅 Firebase 文档read data once它只监听缓存 也是不正确的,它绝对从服务器获取当前值(再次阅读文档)。您是说您的应用收到一个 JSON 节点 candidates 并且您想知道这些特定用户是否在线并且他们的状态是否存储在 Firebase 中?
  • 我已经非常彻底地阅读了文档,并且花了很多天试图弄清楚观察和听众是如何工作的。文档没有解释这一点,但这是一个一致的结果,您可以测试:如果该值之前已从服务器读取,它将存储在缓存中,因此下次您观察SingleEvent 时,它将从缓存中获取。如果缓存中没有值,则observeSingleEvent 将从服务器获取数据。但这不能用于检查用户是否在线,我们需要始终获取服务器值。
  • 如果你附加一个监听器,你可以看看你是否打印出返回的数据,它总是会立即返回2个结果,一个来自缓存,一个来自服务器。这证明,如果有可用的缓存,单次观察只会导致缓存读取。

标签: swift firebase firebase-realtime-database


【解决方案1】:

让我重申一下目标。

为应用提供了大量用户 ID(如 JSON),目标是了解哪些用户在线。

这里有两个选项:

我们从用户节点开始

users
  uid_0
    name: "Frank"
    online: true
  uid_1
    name: "Jeff"
    online: false

当接收到我们要检查的用户的 JSON 时,我们将其转换为 Array 以便于访问。

那么,我们有几个选择

1) 遍历数组并为每个 uid 观察SingleEvent 以获取其当前状态。这不会是一个查询,因为我们可以直接访问 uid,因此与查询相比它会相当轻量级。*

let arrayToTest = [String]() //the array of uid's to test
for uid in arrayToTest {
    let thisUserRef = self.ref.child("users").child(uid)
    thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
        let dict = snapshot.value as! [String: Any]
        let status = dict["online"] as! Bool
        print("uid: \(uid) online status is: \(status)")
    })
}

2) 让 Firebase 完成繁重的工作,方法是向用户节点添加一个 .childAdded 事件,该事件将一次遍历所有用户。

将快照中的 uid 与数组中的 uid 进行比较,如果匹配,则获取其状态(在线/离线),如果数组中不存在,则忽略它。

let usersRef = self.ref.child("users")
let arrayToTest = [String]() //the array of uid's to test
usersRef.observe(.childAdded, with: { snapshot in
    let dict = snapshot.value as! [String: Any]
    let uid = dict["user_id"] as! String
    if arrayToTest.contains(uid) {
        let status = dict["online"] as! Bool
        print("uid: \(uid) online status is: \(status)")
    }
})

选项 2) 通常会更好,因为它一次只加载一个,而 Firebase 正在完成所有工作 - 我们只是检查快照中的 uid 是否存在于代码中。

3)

作为第三种选择,我们可以利用查询。请注意,在前两个选项中,我们使用轻量级观察函数检索数据。相比之下,查询更繁重并占用更多资源 - 但是,快照结果仅限于那些已登录的用户,如果有大量用户可能是最佳选择。

let usersRef = self.ref.child("users")
let queryRef = usersRef.queryOrdered(byChild: "online").queryEqual(toValue: true)
let arrayToTest = [String]() //the array of uid's to test
queryRef.observe(.childAdded, with: { snapshot in
    let dict = snapshot.value as! [String: Any]
    let uid = dict["user_id"] as! String
    if arrayToTest.contains(uid) {
        print("uid: \(uid) online status is: true")
    }
})

*Firebaser 对 1) 会皱眉头,因为他们强烈建议不要在紧密循环中进行观察。所以 2)如果您想确认每个用户都存在,以及 3)如果您只对在线用户感兴趣。

【讨论】:

  • 我使用 queryEquals 的原因是为了避免返回所有离线用户。返回所有离线用户会消耗大量数据并花费大量时间。我认为目标的措辞需要稍有不同,可能会添加“仅返回在线用户”,因为目前它会将每个用户返回给客户端,无论他们是否在线。这不能使用选项 2) 完成,除非我们使用重组数据,不是吗?
  • @aoeuaoeu 这是一个非常好的评论!我提出 2) 作为一个选项,因为我认为您可能会对用户是否真的存在感兴趣和/或如果他们不在线则采取不同的操作 - 对于这些情况,使用了观察事件。我更新了答案以包括 3),它通过查询来解决您的评论,以将结果限制为仅在线的用户。请注意,对于 10,000 个用户,2) 通过我们的测试在 0.38 秒内迭代所有用户。您的结果可能会因互联网速度而异。
  • 感谢您的回答。对于选项 3,如果我让所有用户都在线,我可能会有数十万用户(也对活动用户使用相同的机制),这会变得非常慢(刚刚测试过)。对于选项 2||3 有效的是,如果不是查询“在线”,我可以查询“用户 ID”,但为此我需要那个讨厌的循环,因为没有 WHERE IN 等价物。无论如何,我想我可以得出结论,在大量用户中,网速问题比使用选项 1 之类的观察单事件循环更成问题,所以遗憾的是,我认为选项 1 是最好的选择:/
  • 事实上,当有很多用户时,拥有 queryequal 似乎会导致 firebase 变慢。唔。我收回我说的话。两者都有问题,但我必须使用 option2
  • @aoeuaoeu 重申一下,即使 users 节点中有数万用户,选项 3) 也只会返回 online 为 true 的那些,如果是几千个用户,它会非常非常快。话虽如此,对于如何处理实际需要同时处理数千个用户的情况,可能有更好的选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-23
  • 1970-01-01
  • 1970-01-01
  • 2022-01-15
  • 2020-01-20
  • 2013-06-21
相关资源
最近更新 更多