Firebase-Tutorial--Getting-Started-中文版

备注: 本教程已由 Attila Hegedüs 更新适配 iOS 10 和 Swift 3,原教程由David East 创作。
原文:https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2
翻译:JoeyChang 转载请标明出处

Firebase 是一个移动后台服务,它可以帮助我们创建具有优秀特性的移动 apps。Firebase 提供以下三个主要服务: a realtime database, user authentication and hosting。通过集成 Firebase iOS SDK, 你几乎不用写一行代码就能创建出非常棒的应用。

Firebase 具有数据库实时性这样的独特性能。

你曾经使用过 pull-to-refresh 去拉新数据么?有了 Firebase,现在你可以忽略那种刷新数据方法了。

当 Firebase 数据库更新时,所有的连接者可以实时获取更新,这就意味着你的 app 可以不用用户交互就能获取数据库当前最新值。

本篇 Firebase 教程中,我们将通过创建一个名叫 Grocr 的具有协作性的grocery list app , 来学习Firebase 的一些基本原理。当我们添加一个项目到列表时,它将实时出现在用户的其它设备中,但是我们并不满足于此,我们还将调整 Grocr 让它可以离线工作,以致即使仅有一个 grocery 数据连接,列表也能保持同步。

通过本文,你将学习到以下技能:

  • 保存数据到Firebase数据库
  • 从 Firebase 实时同步数据
  • 验证 users
  • 在线监控 users
  • 实现离线支持

开始,下载初始项目 Grocr-starter. 它使用 CocoaPods 管理 Firebase 。

在 Xcode 中打开 Grocr.xcworkspace,该项目包含三个view controllers:

  1. LoginViewController.swift.
    现在登录功能还是使用的硬编码 user credentials,稍后我们将优化它。

  2. GroceryListTableViewController.swift.
    这个 controller 是 UITableViewController 子类,它通过 UIAlertController 添加 items 到本地数据库的 list 表格。

  3. OnlineUsersTableViewController.swift.
    该 controller 使用 Firebase’s presence feature 展示所有当前在线 users。

此外,还有两个模型类 GroceryItem.swiftUser.swift 。它们做为 app 的数据模型。

Build and run, 你将看到如下这样效果:

Grocr-Starter

注: 当 build 工程时,我们将看到一些 ‘nullability’ 编译警告。它们来自Firebase,暂时我们先忽略它们,稍后解决。

我们可以点击 Login 进行登录,这将使用一个写死的 user 数据,现在该 app 还只能使用本地数据。接下来我们将调用 Firebase 数据使 app 生动起来。

创建 Firebase 账号

有两个重要步骤:

  1. 创建免费 Firebase 账号
  2. 获取你第一个 app 的 URL

我们可以访问 Getting Started page 进行注册。当我们使用我们谷歌账号共享登录进入 firebase, 我们将看到一个干净的 Firebase 控制台。不要担心费用问题,现在 Firebase 免费版本已经足够强大,够用了。

01-firebase-welcome
创建我们的第一个工程,点击 CREATE NEW PROJECT 。在弹出的对话框中输入项目名称以及你的首选 国家/地区:

02-firebase-create-project

点击 CREATE PROJECT, 我们就可以通过控制面板来管理我们的项目了。

03-firebase-dashboard

这将作为所有 Firebase 服务的容器,我们用它存储数据和授权用户。
选择 Add Firebase to your iOS app 开始我们的项目。本项目的 bundle ID 是 rw.firebase.gettingstarted,所以添加此 id 到 iOS bundle ID 文本框。

04-firebase-add-ios-app-1

点击 ADD APP ,将下载一个 GoogleService-Info.plist 文件。将该文件拖拽到 Xcode 中的 Grocr 项目。

04-firebase-add-ios-app-2

点击 CONTINUE. 接下来一页描述怎样安装 Firebase SDK。

04-firebase-add-ios-app-3

本项目已经替我们集成好了,所以点击 CONTINUE 继续。最后一页说明当 app 启动时怎样连接到 Firebase。

04-firebase-add-ios-app-4

点击 FINISH ,查看新项目细节。

04-firebase-add-ios-app-5

Xcode 打开 GroceryListTableViewController.swift ,添加如下代码,创建 Firebase 连接。

1
let ref = FIRDatabase.database().reference(withPath: "grocery-items")

这个 Firebase 连接使用已提供的 path。在 documentation 中,这些 Firebase 属性被称为 references ,它们指定 Firebase 的位置。

简言之,这些属性可以实现保存和同步数据到给定的位置。

我们发现,base URL 不是必须的,相反,它使用 grocery-items 的 child path。Firebase 数据库是 JSON NoSQL 数据库,所以数据都是保存为 JSON 格式。

JSON 是分等级的 key-value 数据结构 – keys 指的是可以根据它获取其它对象格式的 values 值。JSON data 是一个简单的 key value 对儿树形结构。

在 Firebase 中,key 是一个 URL,value是形如 number, string, boolean , object 的随意的数据。

Structuring Data

无论客户端是什么数据格式,保存到 Firebase 的是 JSON 格式。下面是一个 JSON 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// The root of the tree
{ // grocery-items
"grocery-items": {
// grocery-items/milk
"milk": {
// grocery-items/milk/name
"name": "Milk",

// grocery-items/milk/addedByUser
"addedByUser": "David"
},
"pizza": {
"name": "Pizza",
"addedByUser": "Alice"
},
}
}

在上面的 JSON 中,你可以看到每对儿数据都是以键值对儿形式出现的。我们可以继续遍历树并在更深的位置检索数据。

在上面的例子中,我们可以通过路径检索所有的 grocery item。

1
grocery-items

如果你想获取第一个 grocery item ,你可以通过以下路径获取:

1
grocery-items/milk

因为所有的 Firebase keys 对应paths,所以 key 的名字选择很重要。

Understanding Firebase References

一个基本的原则是,Firebase 引用指向 Firebase 中数据存储的位置。如果我们创建多引用,那么这些引用共享同一个连接。
看如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1
let rootRef = FIRDatabase.database().reference()

// 2
let childRef = FIRDatabase.database().reference(withPath: "grocery-items")

// 3
let itemsRef = rootRef.child("grocery-items")

// 4
let milkRef = itemsRef.child("milk")

// 5
print(rootRef.key) // prints: ""
print(childRef.key) // prints: "grocery-items"
print(itemsRef.key) // prints: "grocery-items"
print(milkRef.key) // prints: "milk"

下面我们解释下:

  1. 我们创建一个到 Firebase 数据库 root 引用。
  2. 使用一个 URL ,我们可以创建一个引用到 Firebase 数据库的子路径。
  3. 通过给 rootRef 传递子路径,我们可以使用 child(_:) 创建子引用,这个引用和上面的引用是一样意思。
  4. 使用 itemsRef ,我们可以创建到 milk 的子引用。
  5. 每个引用都有 key 属性。这个属性和 Firebase 数据库关键字的名字一样。

我们不需要在同一个项目中都添加这样的代码,这里只是出于展示目的进行列举。

Adding New Items to the List

GroceryListTableViewController.swift 的底部,找到 addButtonDidTouch(_:) 方法。

在这里我们要实现通过 UIAlertController 的方式添加一个新的 item 。

在 saveAction 方法内,现在仅仅保存数据到一个本地 array,因此 saveAction 不能同步不同客户端的数据,而且在下次启动 app 时,保存的数据将丢失。

没有人会使用不能记录或者同步他们 grocery 清单数据的 app ! 让我们完善 saveAction 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let saveAction = UIAlertAction(title: "Save",
style: .default) { _ in
// 1
guard let textField = alert.textFields?.first,
let text = textField.text else { return }

// 2
let groceryItem = GroceryItem(name: text,
addedByUser: self.user.email,
completed: false)
// 3
let groceryItemRef = self.ref.child(text.lowercased())

// 4
groceryItemRef.setValue(groceryItem.toAnyObject())
}

注释如下:

  1. 从 alert controller 获取 text field 和它的内容。

  2. 使用当前用户数据创建一个新的 GroceryItem 。

  3. 使用 child(_:) 创建一个子引用,这个引用的 key 是 item 的小写名称,因此如果我们添加一个复制的 item (即使使用大写字母,或者使用混合字母),数据库只保存最后一个。

  4. 使用 setValue(_:) 保存数据到数据库。这个方法期望一个字典格式。GroceryItem 有个 toAnyObject() 方法,可以转换对象为字典格式。

在你可以连接数据库之前,我们还需要配置它。找到 AppDelegate.swift ,并在 application(_:didFinishLaunchingWithOptions:) 返回 true 之前添加如下代码:

1
FIRApp.configure()

默认情况,Firebase 数据库需要用户授权读写权限。在浏览器进入 Firebase 控制面板,选中左边的 Database 选项,设置 RULES 如下:

firebase-db-rules

1
2
3
4
5
6
{
"rules": {
".read": true,
".write": true
}
}

修改后,选择 PUBLISH 按钮进行保存设置。
Build and run. 在 Firebase 控制面板,选择 DATA 标签,并将浏览器窗口紧挨模拟器。当我们在模拟器中添加 item ,我们将看到它会出现在控制面板。

fb-save

现在,我们就有了一个可以实时添加数据到 Firebase 的活生生的 grocery list app!但是虽然 key 特性已经可以运行完好了,但是没有数据添加到table view。

那么我们怎样才能将数据从数据库同步到 table view 呢?

Retrieving Data

我们可以通过 observeEventType(_:withBlock:) 方法异步检索 Firebase 中的数据。

GroceryListTableViewController.swift 的 viewDidLoad() 下添加如下方法:

1
2
3
ref.observe(.value, with: { snapshot in
print(snapshot.value)
})

该方法有两个参数:FIRDataEventType 的一个实例以及一个闭包。

event type 确定我们要监听的事件,.value 监听诸如 add, removed, changed 这样的 Firebase 数据库重点数据改变。

当改变发生,数据库使用最新数据更新 app 显示。

app 在闭包方法中通过接受到的 FIRDataSnapshot 一个实例获知数据改变。snapshot,代表某个特定时间点的数据快照。我们可以通过 value 那个属性获取到 snapshot 的数据。

Build and run,我们将看到,在控制台会有 items 列表数据被打印出来。

1
2
3
4
5
6
7
Optional({
pizza = {
addedByUser = "hungry@person.food";
completed = 0;
name = Pizza;
};
})

Synchronizing Data to the Table View

注意打印日志–现在在 table view 中可以看到 grocery 列表了。

GroceryListTableViewController.swift, 替换之前的代码片段为如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 1
ref.observe(.value, with: { snapshot in
// 2
var newItems: [GroceryItem] = []

// 3
for item in snapshot.children {
// 4
let groceryItem = GroceryItem(snapshot: item as! FIRDataSnapshot)
newItems.append(groceryItem)
}

// 5
self.items = newItems
self.tableView.reloadData()
})
```
以上代码的诸行解释:

1. 添加一个监听器监听 grocery-items 改变了什么。

2. 存储最近一次版本数据到闭包中本地的一个变量中。

3. 监听者闭包返回最近数据的一个 snapshot,这个 snapshot 包含所有的 grocery items,而不是仅仅包含改变的 items。使 snapshot.children ,我们可以循环获取 grocery items 。

4. GroceryItem 结构有一个常用的实例化器,它使用 FIRDataSnapshot
来填充它的属性。snapshot 的值可以为任意类型,可以是 dictionary, array, number, or string。当创建好一个 GroceryItem 实例,它被添加到一个包含最近一次版本数据的数组中。

5. 将最新版本的数据赋值给 items,然后更新 table view,使它展示最新数据。
Build and run. 添加一个 pizza item 怎么样? 它将显示到 table view。

[![fb-sync](http://upload-images.jianshu.io/upload_images/130752-7d4bbd79c43a073c.gif?imageMogr2/auto-orient/strip)](https://koenig-media.raywenderlich.com/uploads/2016/07/fb-sync.gif)
不用刷新,就可以及时获取到更新后的数据。

[![realtime-updates](http://upload-images.jianshu.io/upload_images/130752-982812f3fdca27e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://koenig-media.raywenderlich.com/uploads/2015/07/realtime-updates.png)

#### Removing Items From the Table View

table view 将同步我们所有的改变数据, 但是当我们想删除 pizza 时,现在还不能更新。

为了通知数据库删除数据,我们需要设置一个 Firebase reference,当用户轻扫时候删除 item。

定位到 tableView(_:commit:forRowAt:)。现在,该方法使用 index 移除 array 中的 grocery item。这可以实现功能,但我们还有更好的解决方法。替换为如下实现方式:

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let groceryItem = items[indexPath.row]
groceryItem.ref?.removeValue()
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Firebase 遵从单向数据流模型,因此 viewDidLoad() 的 listener 监听 grocery list 的最新数据。清除 item 触发数据改变。

index path 的 row 被用来获取相关的 grocery item。每个  GroceryItem 拥有一个名为 ref 的 Firebase reference property,调用 它的 removeValue() 将移除我们在 viewDidLoad() 定义的 listener。该listener有一个闭包,它使用最新的数据重新加载表视图。

Build and run. 轻扫 item ,点击删除,我们发现 app 和 Firebase 的数据都消失了。

[![fb-delete](http://upload-images.jianshu.io/upload_images/130752-010e75171151d250.gif?imageMogr2/auto-orient/strip)](https://koenig-media.raywenderlich.com/uploads/2016/07/fb-delete.gif)

Nice work! 我们 items 可以实时删除了。


#### Checking Off Items

现在我们知道了怎么添加、删除以及同步 items ,这很酷。但是当我们实际购物时候会怎样呢?我们会删除我们刚购买的物品么,或者当我们添加购物车时给物品打个标记是否更好?

在以前的纸质时代,人们过去常常把东西从购物清单上划掉,因为我们也将在我们的 app 用现代的方式模仿这个行为。


[![grocery-list](http://upload-images.jianshu.io/upload_images/130752-926e622581550b8f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://koenig-media.raywenderlich.com/uploads/2016/07/grocery-list.png)

打开 *GroceryListTableViewController.swift* ,找到  toggleCellCheckbox(_:isCompleted:) 方法,该方法可以根据 item 是否完成来切换UITableViewCell 的必要视图属性。

当 table view 第一次加载后,刚方法在tableView (_:cellForRowAtIndexPath:) 中会被调用,以及当用户点击 cell 时也会被调用。

替换  tableView(_:didSelectRowAt:) 方法为如下:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 1
guard let cell = tableView.cellForRow(at: indexPath) else { return }
// 2
let groceryItem = items[indexPath.row]
// 3
let toggledCompletion = !groceryItem.completed
// 4
toggleCellCheckbox(cell, isCompleted: toggledCompletion)
// 5
groceryItem.ref?.updateChildValues([
“completed”: toggledCompletion
])
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
以下为详细注解:
1. 使用 cellForRow(at:) 确定用户点击的 cell。
2. 根据 index path 的 row 获取对应的 GroceryItem。
3. 改变 grocery item 的 completed 的状态。
4. 调用 toggleCellCheckbox(_:isCompleted:) 更新 cell 的属性。
5. 在 updateChildValues(_:) 方法中,通过传递字典参数,更新Firebase。该方法与 setValue(_:) 不同,因为它只应用更新,而setValue(_:) 具有破坏性,并在该引用中替换整个值。

Build and run. 点击一个 item,我们就可以看到该行被勾号标记并排序。

[![fb-toggle](http://upload-images.jianshu.io/upload_images/130752-9aac977cd7917893.gif?imageMogr2/auto-orient/strip)](https://koenig-media.raywenderlich.com/uploads/2016/07/fb-toggle.gif)
恭喜,我们已经完成了一个相当漂亮的 grocery list app 。

#### Sorting the Grocery List

如果把 ice cream 放在未排序的标记里面,有时我们可能会忘记它。现在让我们进行些优化。

如果可以把已选中的 items 自动移动到列表底部,我们的 app 将更加令人喜欢。这样,未被标记的 items 可以更容易被我们发现。

使用 [Firebase queries](https://firebase.google.com/docs/database/ios/retrieve-data#sorting_and_filtering_data), 我们可以根据任意属性对列表进行排序,在*GroceryListTableViewController.swift*, 更新 viewDidLoad() 方法:

ref.queryOrdered(byChild: “completed”).observe(.value, with: { snapshot in
var newItems: [GroceryItem] = []

for item in snapshot.children {
let groceryItem = GroceryItem(snapshot: item as! FIRDataSnapshot)
newItems.append(groceryItem)
}

self.items = newItems
self.tableView.reloadData()
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
通过关键词 “ completed”,使用 Firebase 引用 queryOrdered(byChild:) 对数据进行排序。

由于列表需要完成顺序,所以 completed 键将传递给查询。然后,queryOrdered(byChild:)返回一个引用,通知服务器以有序的方式返回数据。

Build and run. 点击一行,使其置换为已完成状态,我们将看到,它神奇地自动移动到了最后一行。

[![fb-order](http://upload-images.jianshu.io/upload_images/130752-07b885c6c88401c3.gif?imageMogr2/auto-orient/strip)](https://koenig-media.raywenderlich.com/uploads/2016/07/fb-order.gif)

哇! 我们现在真的让购物变得更容易了。跨多个用户同步数据,似乎应该足够简单,例如,与一个重要的其他用户或 housemate。这听起来像…身份验证!

#### Authenticating Users

Firebase 有一个  [authentication service](https://firebase.google.com/docs/auth/),它允许 apps 验证不同的提供者,我们可以使用 Google, Twitter, Facebook, Github, email & password, 匿名, 甚至 custom backends 这些方式。这里我们使用邮箱和密码方式进行身份认证,因为这种方式是最简单的。

进入 Firebase dashboard ,点击  *Auth*,激活邮箱密码认证。

[![fb-auth-1](http://upload-images.jianshu.io/upload_images/130752-491d0ef2711afaf0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://koenig-media.raywenderlich.com/uploads/2016/07/fb-auth-1.png)

选中 *SIGN-IN METHOD* 标签栏,再在 *Sign-in providers* 那一节选中*Email/Password* 行,切换 *Enable* 并点击 *SAVE*:

[![authentication](http://upload-images.jianshu.io/upload_images/130752-d0ce6b1077067eb8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://koenig-media.raywenderlich.com/uploads/2016/07/authentication.png)

Firebase 存储账户信息到 keychain,因此最后一步,在项目中,切换到 target’s Capabilities 打开 *Keychain Sharing* 开关。

[![keychain-sharing](http://upload-images.jianshu.io/upload_images/130752-6d1bd69c54af152a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://koenig-media.raywenderlich.com/uploads/2016/07/keychain-sharing.png)

现在,我们已经可以使用邮箱和密码进行身份认证了。

##### Registering Users

在 *LoginViewController.swift*,找到 signUpDidTouch(_:) 方法,这里会弹出 UIAlertController 让用户注册账号,定位到 saveAction 方法,添加以下代码到方法块儿。

// 1
let emailField = alert.textFields![0]
let passwordField = alert.textFields![1]

// 2
FIRAuth.auth()!.createUser(withEmail: emailField.text!,
password: passwordField.text!) { user, error in
if error == nil {
// 3
FIRAuth.auth()!.signIn(withEmail: self.textFieldLoginEmail.text!,
password: self.textFieldLoginPassword.text!)
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
以上代码解释:
1. 从弹框中获取邮箱和密码。
2. 调用 Firebase 方法 createUser(withEmail:password:),传递邮箱和密码给它。
3. 如果执行没有错误,用户账号即被创建。但是,我们还要再进行一下登录操作 signIn(withEmail:password:) ,同样需要传递邮箱和密码。

Build and run. 点击 *Sign up* ,键入邮箱和密码,点击保存。现在 view controller 还不能在登录成功后导航到其它地方。我们刷新 Firebase *Login & Auth* ,我们将看到新建的用户。

[![fb-register-user](http://upload-images.jianshu.io/upload_images/130752-30037a7aab9db697.gif?imageMogr2/auto-orient/strip)](https://koenig-media.raywenderlich.com/uploads/2016/07/fb-register-user.gif)

喔!我们的 app 现在可以让用户注册并进行登录了,不过我们先不要庆祝,我们还需要再做些优化,好使用户更好的使用它。

##### Logging Users In

*Sign up*  按钮可以注册和登录,然而  *Login* 现在还什么都做不了,因为我们还没有给它绑定验证。

到 *LoginViewController.swift*, 找到 loginDidTouch(_:) 方法,修改如下:

@IBAction func loginDidTouch(_ sender: AnyObject) {
FIRAuth.auth()!.signIn(withEmail: textFieldLoginEmail.text!,
password: textFieldLoginPassword.text!)
}

1
2
3
4
5
6
当用户点击 *Login* 时,这些代码将验证用户信息。
我们接下来需要在用户登录成功后导航到下一个页面。

##### Observing Authentication State

Firebase 有可以监控用户验证状态的观察者。这里是添加 segue 最好的地方。在 *LoginViewController*: 添加如下代码:

override func viewDidLoad() {
super.viewDidLoad()

// 1
FIRAuth.auth()!.addStateDidChangeListener() { auth, user in
// 2
if user != nil {
// 3
self.performSegue(withIdentifier: self.loginToList, sender: nil)
}
}
}

1
2
3
4
5
6
7
8
9
注释如下:
1. 使用  addStateDidChangeListener(_:) 创建验证观察者。该 block 被传入两个参数:auth 和 user。

2. 测试 user 的值,如果验证通过,返回用户信息,如果验证失败,返回 nil 。

3. 验证成功,进行页面跳转。传输 sender 为 nil 。这看起来有些奇怪,但是稍后我们将在 *GroceryListTableViewController.swift* 进行设置。

##### Setting the User in the Grocery List
在 *GroceryListTableViewController.swift* 文件 viewDidLoad(): 方法底部添加如下代码:

FIRAuth.auth()!.addStateDidChangeListener { auth, user in
guard let user = user else { return }
self.user = User(authData: user)
}

1
2
3
4
5
6
7
8
9
10
11
这里我们添加了一个 Firebase auth object 的验证观察者,当用户成功登录时,依次分配用户属性。

Build and run. 如果用户已经登录,app 将跳过 LoginViewController 直接导航到 GroceryListTableViewController. 当用户添加 items ,他们的 email 将显示到 cell 的详情里面。

[![fb-user-add](http://upload-images.jianshu.io/upload_images/130752-d600c0f675c1d508.gif?imageMogr2/auto-orient/strip)](https://koenig-media.raywenderlich.com/uploads/2016/07/fb-user-add.gif)

Success! app 现在已经有了基本的用户验证功能。


##### Monitoring Users’ Online Status
现在既然我们的 app 已经拥有了用户验证功能,那是时候添加监控哪个用户在线功能了。打开 *GroceryListTableViewController.swift* ,添加如下 property:

let usersRef = FIRDatabase.database().reference(withPath: “online”)

1
2
3
4

这是一个指向存储在线用户列表的在线位置的Firebase引用。

下一步,在 viewDidLoad() 方法下添加如下代码到  addStateDidChangeListener(_:) 闭包的下面。

// 1
let currentUserRef = self.usersRef.child(self.user.uid)
// 2
currentUserRef.setValue(self.user.email)
// 3
currentUserRef.onDisconnectRemoveValue()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
注释如下:
1. 使用用户的 uid 创建一个 child 引用,当 Firebase 创建一个账号时,这个引用会被生成。
2. 使用这个引用保存当前用户的 email.
3. 当 Firebase 连接关闭的时候,例如用户退出 app , 调用 currentUserRef 的 onDisconnectRemoveValue(),删除位置引用的值。这可以完美监控离线用户。

Build and run. 当 view 加载时,当前用户的电子邮件,会被添加在当前在线位置的一个子节点。

[![fb-monitoring](http://upload-images.jianshu.io/upload_images/130752-b36f0c1581a54d0d.gif?imageMogr2/auto-orient/strip)](https://koenig-media.raywenderlich.com/uploads/2016/07/fb-monitoring.gif)

Great! 现在当用户数量增加时,是时候改变 bar button item 的个数了。

##### Updating the Online User Count

仍然在 *GroceryListTableViewController.swift* 的 viewDidLoad() 方法下添加如下代码:

usersRef.observe(.value, with: { snapshot in
if snapshot.exists() {
self.userCountBarButtonItem?.title = snapshot.childrenCount.description
} else {
self.userCountBarButtonItem?.title = “0”
}
})

1
2
3
4
5
这创建一个观察者监控在线用户,当用户在线或者离线,userCountBarButtonItem 的 title 随之更新。

##### Displaying a List of Online Users

 打开 *OnlineUsersTableViewController.swift*,在 class 的 property section 添加一个本地引用到 Firebase 的在线用户记录。

let usersRef = FIRDatabase.database().reference(withPath: “online”)

1
然后,在viewDidLoad(), 替换代码

currentUsers.append(“hungry@person.food”)

1
为如下:

// 1
usersRef.observe(.childAdded, with: { snap in
// 2
guard let email = snap.value as? String else { return }
self.currentUsers.append(email)
// 3
let row = self.currentUsers.count - 1
// 4
let indexPath = IndexPath(row: row, section: 0)
// 5
self.tableView.insertRows(at: [indexPath], with: .top)
})

1
2
3
4
5
6
7
8
9
10
11
12
代码注释如下:
1. 创建一个 children added 监听器,添加到被 usersRef 管理的位置。这与值侦听器不同,因为只有添加的 child 被传递到闭包。

2. 从 snapshot 获取值,并赋值给本地变量 array。
3. 因为 table view 的坐标从 0 开始计算,当前的 row 总是等于 array 的个数 -1。
4. 使用当前 row index 创建一个 NSIndexPath.

5. 使用动画从顶部添加一行到 table view.

这将只渲染添加的条目,而不是重新加载整个列表,而且还可以指定一个漂亮的动画。:]

由于用户可以脱机,table 需要对被删除的用户做出反应。在我们刚刚添加的代码下面添加以下内容:

usersRef.observe(.childRemoved, with: { snap in
guard let emailToFind = snap.value as? String else { return }
for (index, email) in self.currentUsers.enumerated() {
if email == emailToFind {
let indexPath = IndexPath(row: index, section: 0)
self.currentUsers.remove(at: index)
self.tableView.deleteRows(at: [indexPath], with: .fade)
}
}
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

这只是添加了一个观察者,它侦听被删除的 usersRef 引用的子元素。它在本地数组中搜索电子邮件的值,以找到相应的子条目,一旦找到,它就从表中删除相关的行。

Build and run.

在 Firebase 用户仪表板上点击 *Online *,当前用户的电子邮件将出现在表格中。使用一些技巧,可以在网上添加一个用户,一旦你做了,它就会显示在列表中。在仪表板上单击*删除*按钮,用户就会从 table 中消失….

[![fb-users-table](http://upload-images.jianshu.io/upload_images/130752-8c79b48329764b6c.gif?imageMogr2/auto-orient/strip)](https://koenig-media.raywenderlich.com/uploads/2016/07/fb-users-table.gif)

Booyah! 当用户被添加和删除的时候,table 随之更新了。

[![monintoring-users](http://upload-images.jianshu.io/upload_images/130752-c8da3f05b344b399.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://koenig-media.raywenderlich.com/uploads/2015/07/monintoring-users.png)

##### Enabling Offline

杂货店因不稳定的数据连接而臭名昭著。你会认为他们现在都有了Wi-Fi,但是没有!

不过没关系,我们只需设置数据库离线工作。打开 * AppDelegate*,在(_:didFinishLaunchingWithOptions:) 底部方法返回 true 之前,添加如下代码:

FIRDatabase.database().persistenceEnabled = true
```

是的,就是这样! 就像我们的应用能够离线运行一样。当 app 重启,一旦建立网络连接,离线更新也将作用于我们的 Firebase 数据库。Oooh-ahhhh !

Where To Go From Here?

我们可以在这里下载 Grocr-final完整项目。

注意:下载完后,我们仍需要添加自己的 GoogleService-Info.plist 和 设置允许Keychain sharing

在这个Firebase教程中,我们通过构建一个协作的购物清单 app 了解了Firebase的基础知识,我们已经实现了将数据保存到一个 Firebase 数据库、实时同步数据、认证用户、监视在线用户状态以及实现了离线支持。所有这些都是在没有写一行服务器代码的情况下完成的! :]

如果你对 Firebase 感兴趣,请查看文档 documentation,以及 Firebase 提供的示例。

如果您对这个Firebase教程、Firebase或示例应用有任何意见或问题,请加入下面的论坛讨论!

2017.09.06 19:02
       上海 虹桥V1 
Joey Chang wechat
扫描二维码,关注我的公众号
坚持原创技术分享,您的支持将鼓励我继续创作!