ios - 如何通过在 swift 中实现观察者从 Firebase 实时数据库中获取嵌套数据

标签 ios swift firebase uitableview firebase-realtime-database

Firebase 数据结构

  • lastLocations (batteryStatus, lat, long, timestamp, uid)
  • 个人资料(姓名、电话号码、图片、uid)
  • userFriends(基于 uid -> 有多少 friend -> conversationUid、friendStatus、notify、phoneNumber、uid)

  • 我的代码:
  • 我已经为它创建了 tableview 和 xib。
  • 我已经为最后一个位置、个人资料、用户 friend 创建了模型。
  • 我已经获取了 friend 列表,但在观察 .ChildAdded
  • 我的用户名 zzV6DQSXUyUkPHgENDbZ9EjXVBj2
  • 链接:https://drive.google.com/file/d/19cnkY03MXjrTFgzzPCdvmOuosDRvoMx9/view?usp=sharing

  • 问题:
  • 不知道如何通过观察者有效地使用好友列表获取位置和个人资料,因此任何变化都会反射(reflect)出来。 Firebase 是异步进程。
  • 观察者实现,因此数据不会每次都完全加载

  • 要达到的结果:
  • 我需要根据我的 uid 在 tableview 上显示好友列表(姓名、头像、电池状态、经纬度(地址)、时间戳)。

  • Firebase JSON
    {
      "lastLocations": {
        "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
          "batteryStatus": 22,
          "latitude": 40.9910537,
          "longitude": 29.020425,
          "timeStamp": 1556568633477,
          "uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
        },
        "zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
          "batteryStatus": 88,
          "latitude": 41.0173995,
          "longitude": 29.1406086,
          "timeStamp": 1571778174360,
          "uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
        }
      },
      "profiles": {
        "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
          "fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
          "name": "Skander",
          "phoneNumber": "+95644125503",
          "uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
        },
        "zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
          "fcmToken": "enMneewiGgg:APA91bHyA4HypWUYhxGTUTTch8ZJ_6UUWhEIXRokmR-Y-MalwnrtV_zMsJ9p-sU_ZT4pVIvkmtJaCo7LFJYJ9ggfhc1f2HLcN9AoIevEBUqyoMN-HDzkweiUxAbyc84XSQPx7RZ1Xv",
          "name": "Murad",
          "phoneNumber": "+915377588674",
          "picture": "profile/zzV6DQSXUyUkPHgENDbZ9EjXVBj2/a995c7f3-720f-45bf-ac58-b2df934e3dff.jpeg",
          "uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
        }
      },
      "userFriends": {
        "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
          "zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
            "conversationUid": "-L_w2yi8gh49GppDP3r5",
            "friendStatus": "STATUS_ACCEPTED",
            "notify": true,
            "phoneNumber": "+915377588674",
            "uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
          }
        },
        "zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
          "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
            "conversationUid": "-L_w2yi8gh49GppDP3r5",
            "friendStatus": "STATUS_ACCEPTED",
            "notify": true,
            "phoneNumber": "+915644125503",
            "uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
          }
        }
      }
    
    }
    

    swift 功能:
    func getFrndDataList(){
            AppData.removeAll()
            ref.child("userFriends").child("zzV6DQSXUyUkPHgENDbZ9EjXVBj2").observe(.childAdded, with: { (snapshot) in
    
               guard let data = try? JSONSerialization.data(withJSONObject: snapshot.value as Any) else { return }
               let frndList = try? JSONDecoder().decode(Friend.self, from: data)
    
               self.AppData.append(frndList!)
               self.tableView.reloadData()
               print([frndList])
            })
        }
    

    最佳答案

    注意:写完这个答案后,我意识到它很长,但这是一个很大的问题,有很多要解决的问题。

    我的第一个建议是改变结构,因为它对数据的处理过于复杂。此外,还有一些不需要的重复数据,因此也应该更改。例如,这是您的个人资料节点

      "profiles": {
        "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
          "fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
          "name": "Skander",
          "phoneNumber": "+95644125503",
          "uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1" <- remove this, not needed.
        },
    

    如您所见,每个子节点都有一个用户 ID 的键。但是,您也将用户 ID 存储为子节点。它们的关键是 uid 并且将始终可用,因此不需要在那里复制,并且应该删除子节点。

    根据评论,这是一个更好的结构
    /users
       FTgzbZ9uWBTkiZK9kqLZaAIhEDv1
          "batteryStatus": 22,
          "latitude": 40.9910537,
          "longitude": 29.020425,
          "timeStamp": 1556568633477,
          "fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
          "name": "Skander",
          "phoneNumber": "+95644125503",
          "conversationUid": "-L_w2yi8gh49GppDP3r5",
          "friendStatus": "STATUS_ACCEPTED",
          "notify": true,
          "phoneNumber": "+915377588674",
    

    然后,为了跟踪用户的 friend ,它变成了这个
    /userFriends
       zzV6DQSXUyUkPHgENDbZ9EjXVBj2 //this user
          FTgzbZ9uWBTkiZK9kqLZaAIhEDv1: true //their friend
          IRoo0lbhaihioSSuFETngEEFEeoi: true //another friend
    

    为了加载这个用户的 friend ,我们在/userFriends/this_users_id 读取数据,然后遍历子节点加载数据以在 tableView 中显示

    让我们从一个用于保存每个 friend 数据的对象开始,然后是一个将用作 tableView 数据源的数组
    class FriendClass {
        var uid = ""
        var name = ""
        //var profilePic
        //var batteryStatus
    
        init(withSnapshot: DataSnapshot) {
            self.uid = withSnapshot.key
            self.name = withSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
        }
    }
    
    var myFriendsDataSource = [FriendClass]()
    

    然后是读取用户节点的函数,遍历用户 friend uid 并读入每个用户数据,填充 FriendClass 对象并将每个对象存储在一个数组中。请注意 self.ref 指向我的火力基地。
    func loadUsersFriends() {
        let uid = "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
        let myFriendsRef = self.ref.child("userFriends").child(uid)
        myFriendsRef.observeSingleEvent(of: .value, with: { snapshot in
            let uidArray = snapshot.children.allObjects as! [DataSnapshot]
            for friendsUid in uidArray {
                self.loadFriend(withUid: friendsUid.key)
            }
        })
    }
    
    func loadFriend(withUid: String) {
        let thisUserRef = self.ref.child("users").child(withUid)
        thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
            let aFriend = FriendClass(withSnapshot: snapshot)
            self.myFriendsDataSource.append(aFriend)
        })
    }
    

    现在我们有了读取数据的代码,您还想观察变化。有很多选择,但这里有两个。

    1)我称之为蛮力。

    只需将 .childChanged 观察者附加到/users 节点,如果发生变化,则将更改的节点传递给观察者。如果该节点的键与 myFriendsDataSource 数组中的键匹配,则更新数组中的该用户。如果不匹配,则忽略它。
    func watchForChangesInMyFriends() {
        let usersRef = self.ref.child("users")
        usersRef.observe(.childChanged, with: { snapshot in
            let key = snapshot.key
            if let friendIndex = self.myFriendsDataSource.firstIndex(where: { $0.uid == key} ) {
                let friend = self.myFriendsDataSource[friendIndex]
                print("found user \(friend.name), updating")
                //friend(updateWithSnapshot: snapshot) //leave this for you to code
            }
        })
    }
    

    2) 选择性观察

    为此,我们只需将 .childChanged 观察者附加到每个 friend 节点 - 这可以在上面的代码示例中完成
    func loadFriend(withUid: String) {
        let thisUserRef = self.ref.child("users").child(withUid)
        thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
            let aFriend = FriendClass(withSnapshot: snapshot)
            self.myFriendsDataSource.append(aFriend)
            //add an observer to this friends node here.
        })
    }
    

    最后一件事:我没有解决这个问题
    "friendStatus": "STATUS_ACCEPTED",
    

    我认为只有您接受的 friend 才在 friend 列表中,因此用途有点不清楚。但是,如果你想使用它,你可以这样做
    /userFriends
       zzV6DQSXUyUkPHgENDbZ9EjXVBj2 //this user
          FTgzbZ9uWBTkiZK9kqLZaAIhEDv1: "STATUS_ACCEPTED"
          IRoo0lbhaihioSSuFETngEEFEeoi: "STATUS_DECLINED"
    

    然后当您遍历要加载的 friend 时,请忽略那些被拒绝的 friend 。

    如果您必须保留当前的结构(我不推荐),则此答案中的技术也适用于该结构,但是,它将需要更多代码,并且您将移动很多不需要的额外内容数据,因此 Firebase 账单会更高。

    关于ios - 如何通过在 swift 中实现观察者从 Firebase 实时数据库中获取嵌套数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58538117/

    相关文章:

    ios - 在 Firebase/Swift 上为两个匹配的用户创建聊天室的首选方法是什么?

    iphone - 为什么 UITableView 调用 UIScrollView 的委托(delegate)方法?

    ios - 如何在带有重复的 animateWithDuration 的每个动画之间更改 UILabel 的文本?

    ios - 在自调整单元格、iOS8 及更高版本中为 UITextView 启用滚动

    ios - UIAlert 和 View Controller 的错误

    ios - 使用实例填充 UITableview 来填充数组

    ios - 多个 Segues 到一个 View Controller (如何使其在 Storyboard中看起来不错)

    swift - Swift 中 available 的使用

    ios - Firebase 重置密码 Swift

    firebase - 当我想获取我的图片下载链接 Flutter 时遇到问题(请求没有应用检查 token 。)