# FireBase for SwiftUI小纸条（三）

如果你已经[创建好了Firebase和Xcode项目](https://blog.hdcola.one/firebase-for-swiftui-1)，并且也已经[将Realtime Database相关设置完成](https://blog.hdcola.one/firebase-for-swiftui-2)。接下来，我们开始尝试做从Realtime Database获取数据的操作。

## 获取Realtime Database数据

### 一次性读取一个值

为了进行读取操作，我们需要准备一个View和一个ViewModel。首先是我们的ViewModel：

```swift
import Foundation
import FirebaseDatabase
import FirebaseDatabaseSwift

class ReadViewModel: ObservableObject{
    var ref = Database.database().reference()
    
    @Published var value: String? = nil
    
    func readVale(){
        ref.child("game1").observeSingleEvent(of: .value) { snapshot in
            self.value = snapshot.value as? String
        }
    }
}
```

再准备一个View来显示它的内容：

```swift
import SwiftUI

struct ReadView: View {
    @StateObject var vm = ReadViewModel()
    
    var body: some View {
        VStack{
            if vm.value == nil {
                Text("The place will display value...")
                    .padding()
                    .background(.gray)
            }else{
                Text(vm.value!)
                    .padding()
                    .background(.gray)
            }
            
            Button{
                vm.readVale()
            }label: {
                Text("Read")
            }
            .buttonStyle(.borderedProminent)
            
        }
    }
}
```

这个view启动时会是这样的：

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1661127273221/jUx_zSK6J.png align="left")

运行起来，会发现界面没有任何，变化，原因很简单，你需要在服务器控制台，加一个子节点，名叫`game1`，这就是ViewModel中` ref.child("game1")`会指向的子节点，给它增加任何值：

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1661127923436/E-ti1Y2yK.png align="left")

运行起我们的View，我们会发现每点击一下Read按钮，它就会去读取一次数值，当服务器更新后，再点一次Read，就会再读取一次数据：

![readValue.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1661128498299/ot1uf1PvJ.gif align="left")

`observeSingleEvent(of: .value)`在做什么呢？用官方的原话，它对于只需加载一次且预计不会频繁变化或不需要主动侦听的数据能立即返回本地缓存中的值，而不必检查服务器上更新后的值。

### 观察数值变化

如果我们想让value的内容能实时被同步下来，Realtime Database也提供了非常方便的方法，我们只需要使用以下代码替代readValue函数的内容：

```swift
        ref.child("game1").observe(.value) { snapshot in
            self.value = snapshot.value as? String ?? "Load failed"
        }
```

我们就得到了一个实时同步版本：

![observerValueChange.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1661131675621/eHG3KJ23s.gif align="left")

这时所使用的`.boserve(.value)`方法会在server上数值发生变化时，使用snapshot做为参数同步执行指定的匿名函数。

### 读取一个对象

我们在[FireBase for SwiftUI小纸条（二）- 在SwiftUI中使用Realtime Database写入数据](https://blog.hdcola.one/firebase-for-swiftui-2)中我们写入过一个叫User的对象，但是之前它只加入了Encodable，我们现在要为它加入遵循Decodable的协议：

```swift
class User: Encodable,Decodable{
    var username: String = ""
    var level: Int = 1
}
```

接下来，为ReadViewModel加入一个属性和一个方法：

```swift
    @Published var object : User? = nil

    func readObject(){
        ref.child("game2").observe(.value) { snapshot in
            do{
                self.object = try snapshot.data(as: User.self)
            }catch{
                print("Can't convert to User object")
            }
        }
    }
```

所以我们将会使用game2子节点的内容来还原到object中去，而这个object是User的一个实例。所以我们在数据库中加入game2子节点，并加入username和level属性：

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1661181124788/wsmX9zLjm.png align="left")

为了显示一个Object，我们将ReadView也进行一些改进：

```swift
import SwiftUI

struct ReadView: View {
    @StateObject var vm = ReadViewModel()
    
    var body: some View {
        VStack{
            if vm.value == nil {
                Text("The place will display value...")
                    .padding()
                    .background(.gray)
            }else{
                Text(vm.value!)
                    .padding()
                    .background(.gray)
            }
            
            Button{
                vm.observerValueChange()
            }label: {
                Text("Read")
            }
            .buttonStyle(.borderedProminent)
            if vm.object == nil {
                Text("The place will display object...")
                    .padding()
                    .background(.gray)
            }else{
                Text("username:\(vm.object!.username)\nlevel:\(vm.object!.level)")
                    .padding()
                    .background(.gray)
            }
            
            Button{
                vm.readObject()
            }label: {
                Text("Read")
            }
            .buttonStyle(.borderedProminent)
        }
    }
}
```

再来试试结果：

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1661181218300/ZssimAfWA.png align="left")

### 读取一组对象

Realtime Database也提供了读取一组对象的方法，甚至你也可以用侦听的方式来读取这一组对象：

```swift
    func readListObject(){
        ref.observe(.value){parentSnapshot in
            guard let children = parentSnapshot.children.allObjects as? [DataSnapshot] else{
                return
            }
            
            self .listObject = children.map({ snapshot in
                return try! snapshot.data(as: User.self)
            })
        }
    }
```

## 附

- [DataSnapshot类参考](https://firebase.google.com/docs/reference/swift/firebasedatabase/api/reference/Classes/DataSnapshot)
- [FirebaseDatabaseSwift提供的DataSnapshot类扩展](https://firebase.google.com/docs/reference/swift/firebasedatabaseswift/api/reference/Extensions/DataSnapshot)
