原文:Introduction to Firebase: Building a Simple Social App in Swift
作者:MATTHEW MAHER
译者:kmyhy
現在的 App ,既想獲得用戶歡心,又要完全自我實現是不太可能了。iOS 開發者們已經開始尋求蘋果之外的工具和資源來訪問數據和存儲。其中最吸引人的一種方案就是 Baas(後端及服務)。
其中,Google 的 Firebase 是當下最流行和最好用的 Baas。Firebase 在性能、實現和可維護性上都要高人一等。Firebase 的最讓人所知的一個重要特點就是,它是一個完全以 JSON 為存儲格式的實時數據庫。在數據庫中的任何改變都會立即同步到所有連接該數據庫的客戶端和設備。也就是說,Firebase 超級快,幾乎任何數據都是實時刷新的。
Firebase 支持用戶驗證,所有數據通過 SSL 進行傳輸。用戶驗證可以通過郵箱和密碼,或者 Facebook、Twitter、Github、Google 及自定義認證等方式。
除了 iOS,Firebase 還支持 Android 和 JavaScript SDK。所有平台都可以共用同一個數據庫,Firebase 自動根據 App 調整庫大小。
但擁有所有這些特性的 Firebase 的報價卻極其低廉。 這其中會不會有什麼貓膩呢?
但確實沒有任何貓膩。截止到本教程為止,Firebase 對於並發連接數在 100 以內是完全免費的。這完全超乎我們想象,100 個開放的網絡連接對於絕大部分主流 App 來說都已足夠。如果每月支付 49 美元,就不再受這個限制。
關於 FirebaseJokes
今天,我們會用 Firebase 創建一個小笑話 App。在這個 App 中,我們允許用戶以郵箱和密碼創建一個賬號并進行登錄,表格視圖會實時刷新。如果另一個用戶發表了新的笑話,這個表格會立即刷新。更有趣的是,我們還添加了一個點讚功能,這樣那些超級搞笑的笑話就會獲得高分。
這是我們準備在 FirebaseJokes 中實現的功能:
開始項目在這裡下載。
首先,打開 Main.Storyboard ,看一眼 App 的 UI 設計。
在實現整個 App 的過程中,我們會按照順序、順理成章地、一步一步地讓你理解 Firebase 的用法。這是一個很好的框架,易於使用,非常有意思。現在,先編譯一下 FirebaseJokes。
下載 Firebase
進入 Firebase 官網 ,註冊一個 Firebase 賬號,如果你已經註冊過的話則登錄你的 Firebase 賬號。也可以用 SIGN UP WITH GOOGLE 進行快捷登錄。當你註冊好賬號以後,我們會直接跳過 5 分鐘教程,因為那是針對 Javascript 的,我們需要下載的是 iOS SDK。
在 My First App 下點擊 Manage App,看一眼 Firebase 裡面到底有什麼。這裡有一個全新的東東叫做 Firebase Forge。這是一個超酷的圖形化的調試工具,Forge 教程是非常值得看一下的。Forge 教程會告訴你如何創建 key、value 以及子節點(通過 + 號)。這看起來就是 JSON 嘛,不是嗎?要退出 Forge 教程,點擊右上角的 Dashboard。
新建 App
我們來創建 FirebaseJokes 項目。在 My First App 左邊,點擊虛線框,創建一個新的 App。在 APP NAME 一欄,輸入 Jokes。在 APP URL 一欄,輸入 jokes-your-name(your-name 是你自己的名字)。這個字段必須是唯一的,因為這是訪問你的 App 的真實 Url 地址。最後,點擊 CREATE NEW APP 按鈕,再點擊 Manage App 按鈕。
然後是你的 Forge 窗口。在這裡,我們能夠看到數據的實時變化,同時 App 中也會做相同的變化。我們可以直接在 Forge 中輸入 App 的數據。為了從數據層面理解 App 是如何運行的, 我們將手動輸入一些數據。
- 在 jokes-your-name 一行,點擊 + 號。
- 在 name 字段,輸入 “jokes” 。
- 在新的 jokes 行中,點擊 + 號。
- 在 name 字段,輸入一個隨機數。
- 在隨機數這一行,點擊 + 號。
- 在 name 字段中輸入 “jokeText”。
- 在 value 字段,輸入 “What did one computer say to the other? 11001001010101”。
這裡顯示了一個 joke 對象將是什麼樣子。我們還會加更多的東西到 jokes 中,除此之外還需要一個 users 對象,這個過程和 jokes 差不多。在創建 App 的過程中,我們會不時回Forge 來看一眼這些數據,這是個好習慣。
需要指出的是,所有 Firebase 數據庫中的數據都是以 JSON 格式存儲的。這和 Parse 不同,Firebase 中沒有表或者記錄的概念。向 Firebase 數據庫中存入數據時,實際上是在已有的 JSON 結構中添加一個 key。例如,你剛剛添加的數據其實是這個樣子的:
{ "jokes" : { "e32e223r44" : { "jokeText" : "What did one computer say to the other? 11001001010101" } } }
好了,你已經了解 Firebase 數據庫中的數據是什麼樣子的了,讓我們繼續。
在進入用戶驗證部分之前,我們先刪除剛剛創建的數據,這些數據我們會在代碼中創建。
在 FirebaseJokes 中,我們用郵箱密碼進行用戶驗證。要使用這個特性,在 Forge 左側面板中點擊 Login & Auth。在 Email & Password 中,勾選 Enable Email & Password Authentication。這裡是值得好好看一下的。在這個下面,是關於密碼找回的信息。這裡有一些認證設置也是值得注意的。
安裝 Firebase SDK ,設置 Base URL
要查看 Firebase App 的 Base URL,需要回到 Forge。Forge 頁面的 URL 就是 App 的 URL,把它複製粘貼到 Constants.swift 的 BASE_URL 常量中即可。
import Foundation
let BASE_URL = "https://jokes-matt-maher.firebaseio.com"
然後,我們來將 Firebase SDK 安裝到 App 中。但在此之前,我們需要安裝 CocoaPods。如果你還沒有安裝 CocoaPods,可以參考這裡的安裝指南:CocoaPods。
安裝好 CocoaPods 之後,打開終端程序。運行如下命令,為你的 Xcode 工程初始化 CocoaPods:
cd pod init
然後用 Xcode 打開 Podfile 文件:
open -a Xcode Podfile
編輯 Podfile 的內容為:
platform :ios,'8.0' use_frameworks! pod 'Firebase','>= 2.5.0'
然後,開始下載 Firebase SDK:
pod install
接下來就要用 Xcode 編寫代碼了。確認你是用 FirebaseJokes.xcworkspace
文件來啟動 Xcode 的。
使用 Firebase SDK
首先,為減少工作量,我們需要在 DataService.swift
中進行一些設置。主要是創建一些引用。
import Foundation
import Firebase
class DataService {
static let dataService = DataService()
private var _BASE_REF = Firebase(url: "\(BASE_URL)")
private var _USER_REF = Firebase(url: "\(BASE_URL)/users")
private var _JOKE_REF = Firebase(url: "\(BASE_URL)/jokes")
var BASE_REF: Firebase {
return _BASE_REF
}
var USER_REF: Firebase {
return _USER_REF
}
var CURRENT_USER_REF: Firebase {
let userID = NSUserDefaults.standardUserDefaults().valueForKey("uid") as! String
let currentUser = Firebase(url: "\(BASE_REF)").childByAppendingPath("users").childByAppendingPath(userID)
return currentUser!
}
var JOKE_REF: Firebase {
return _JOKE_REF
}
}
要使用 Firebase,我們需要 import Firebase 框架。 DataService
類是一個專門和 Firebase 打交道的服務類。要讀寫數據,我們需要用 Firebase URL 創建 Firebase 數據庫引用。Base URL 是我們的 App 的數據庫 URL。稍後我們會將 users 和 jokes 以子節點的形式保存。要訪問子節點,只需要將節點名(比如 users)添加到 Base URL 後面即可。為了方便,我們也創建了這兩個子節點的引用。
[ecko_alert color=”green”]
注意:初始化一個數據庫連接並不會真正創建一個到 Firebase 服務器的數據庫連接。不到進行讀寫操作的時候是不會抓取數據的。
[/ecko_alert]
新建用戶賬號
我們將從 CreateAccountViewController.swift
開始。因為要用到 Firebase,所以一開始我們就要 import 它。
import UIKit
import Firebase
class CreateAccountViewController: UIViewController {
在 createAccount()
方法,我們會從 Text Field 獲取字符串并用于創建一個新的用戶。這將調用 Firebase 的 createUser()
方法。編寫這個方法為:
@IBAction func createAccount(sender: AnyObject) {
let username = usernameField.text
let email = emailField.text
let password = passwordField.text
if username != "" && email != "" && password != "" {
// Set Email and Password for the New User.
DataService.dataService.BASE_REF.createUser(email,password: password,withValueCompletionBlock: { error,result in
if error != nil {
// There was a problem.
self.signupErrorAlert("Oops!",message: "Having some trouble creating your account. Try again.")
} else {
// Create and Login the New User with authUser
DataService.dataService.BASE_REF.authUser(email,withCompletionBlock: {
err,authData in
let user = ["provider": authData.provider!,"email": email!,"username": username!]
// Seal the deal in DataService.swift.
DataService.dataService.createNewAccount(authData.uid,user: user)
})
// Store the uid for future access - handy!
NSUserDefaults.standardUserDefaults().setValue(result ["uid"],forKey: "uid")
// Enter the app.
self.performSegueWithIdentifier("NewUserLoggedIn",sender: nil)
}
})
} else {
signupErrorAlert("Oops!",message: "Don't forget to enter your email,password,and a username.")
}
}
假設用戶輸入的內容沒有問題,將創建一個新用戶并登錄到 App。如果缺少必要的信息,則顯示警告。然後,在這個類中再添加一個新方法:
func signupErrorAlert(title: String,message: String) {
// Called upon signup error to let the user know signup didn't work.
let alert = UIAlertController(title: title,message: message,preferredStyle: UIAlertControllerStyle.Alert)
let action = UIAlertAction(title: "Ok",style: .Default,handler: nil)
alert.addAction(action)
presentViewController(alert,animated: true,completion: nil)
}
用戶數據的保存放在 DataService.swift 的 createNewAccount()
方法中:
func createNewAccount(uid: String,user: Dictionary
用戶登錄
首先,import Firebase 到 LoginViewController.swift。這裡,我們將判斷一個用戶是否已經登錄,如果未登錄將讓這個用戶登錄。
在 viewDidAppear()
方法,我們判斷我們保存的 uid 是否為 nil
,以及用戶是否擁有合法賬號。如果是,就會跳過登錄界面,如果否,則要求進行登錄。
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// If we have the uid stored,the user is already logger in - no need to sign in again!
if NSUserDefaults.standardUserDefaults().valueForKey("uid") != nil && DataService.dataService.CURRENT_USER_REF.authData != nil {
self.performSegueWithIdentifier("CurrentlyLoggedIn",sender: nil)
}
}
tryLogin() 方法是一個 IBAction 方法,當用戶點擊登錄按鈕時觸發。實現這個方法和loginErrorAlert
方法:
@IBAction func tryLogin(sender: AnyObject) {
let email = emailField.text
let password = passwordField.text
if email != "" && password != "" {
// Login with the Firebase's authUser method
DataService.dataService.BASE_REF.authUser(email,withCompletionBlock: { error,authData in
if error != nil {
print(error)
self.loginErrorAlert("Oops!",message: "Check your username and password.")
} else {
// Be sure the correct uid is stored.
NSUserDefaults.standardUserDefaults().setValue(authData.uid,forKey: "uid")
// Enter the app!
self.performSegueWithIdentifier("CurrentlyLoggedIn",sender: nil)
}
})
} else {
// There was a problem
loginErrorAlert("Oops!",message: "Don't forget to enter your email and password.")
}
}
func loginErrorAlert(title: String,message: String) {
// Called upon login error to let the user know login didn't work.
let alert = UIAlertController(title: title,completion: nil)
}
Firebase 自帶了用戶郵箱密碼驗證的功能。tryLogin() 方法調用了 Firebase 的 authUser()
方法去驗證是否存在和指定郵箱密碼匹配的賬號存在。如果有,將 uid 保存并導航到 App 主界面。如果沒有,則警告用戶。
現在,用戶註冊和登錄的功能都已經完成了,是時候講一些笑話了。
Joke 類
什麼是一個笑話?稍後我們再來完美地闡釋這個哲學問題,現在我們要說的是 FirebaseJokes App 中的一個小笑話。對於我們來說,一個笑話將用一個 Joke 類來映射。
打開 Joke.swift
,首先 import Firebase 框架。在我們的數據庫中,一個笑話用一個奇怪的 ID 號代表,這個數字是自動產生的。一個 Joke 擁有如下屬性:
- jokeText
- jokeVotes
- username (笑話的作者)
我們可以用 init() 方法初始化一個笑話,這個方法需要一個笑話的 ID(或 key)、笑話的內容(一個字典)作為參數。
class Joke {
private var _jokeRef: Firebase!
private var _jokeKey: String!
private var _jokeText: String!
private var _jokeVotes: Int!
private var _username: String!
var jokeKey: String {
return _jokeKey
}
var jokeText: String {
return _jokeText
}
var jokeVotes: Int {
return _jokeVotes
}
var username: String {
return _username
}
// Initialize the new Joke
init(key: String,dictionary: Dictionary
添加新笑話
在 AddJokeViewController.swift
,import Firebase 庫。在這個控制器中,用戶可以輸入一個笑話,然後這個笑話會立即在所有設備上顯示。
首先是 viewDidLoad()
方法,我們會取得當前用戶的用戶名,并以此作為新笑話的作者。
override func viewDidLoad() {
super.viewDidLoad()
// Get username of the current user,and set it to currentUsername,so we can add it to the Joke.
DataService.dataService.CURRENT_USER_REF.observeEventType(FEventType.Value,withBlock: { snapshot in
let currentUser = snapshot.value.objectForKey("username") as! String
print("Username: \(currentUser)")
self.currentUsername = currentUser
},withCancelBlock: { error in
print(error.description)
})
}
當調用 saveJoke()
時,會創建一個 newJoke 字典,用 jokeField.text 作為 jokeText,用 0 作為當前笑話所獲得的點贊數,用當前用戶作為笑話的作者。將這些值賦給對應的 key,然後調用 DataService 的 createNewJoke()
方法將數據保存到數據庫。
在 AddJokeViewController
類中聲明一個屬性:
var currentUsername = ""
@IBAction func saveJoke(sender: AnyObject) {
let jokeText = jokeField.text
if jokeText != "" {
// Build the new Joke.
// AnyObject is needed because of the votes of type Int.
let newJoke: Dictionary
用戶登出
通常,這個功能應該放在“設置”或者“我”中,但這裡我們卻將它放在了AddJokeViewController.swift
中。也許這本身就是一個玩笑。
logout()
方法中調用 Firebase 的 unauth()
方法去登出一個用戶。除了登出用戶之外,我們還需要從 NSUserDefaults 中刪除該用戶的 uid,并退回到 LoginViewController 界面。
@IBAction func logout(sender: AnyObject) {
// unauth() is the logout method for the current user.
DataService.dataService.CURRENT_USER_REF.unauth()
// Remove the user's uid from storage.
NSUserDefaults.standardUserDefaults().setValue(nil,forKey: "uid")
// Head back to Login!
let loginViewController = self.storyboard!.instantiateViewControllerWithIdentifier("Login")
UIApplication.sharedApplication().keyWindow?.rootViewController = loginViewController
}
如果忘記將用戶的 uid 刪除的話,會導致新用戶在登錄 App 時出現問題,因此我們需要避免這類問題的發生。
顯示全部笑話
終於可以從 Firebase 抓取數據了。我們將在 JokesFeedTableViewController.swift 中用一個 UITableView 列出所有笑話。毫無疑問,這裡也要先 import Firebase。
首先是 viewDidLoad()
方法,在這裡我們會調用 observeEventType()
方法。Firebase 用一個異步監聽器抓取指定數據庫引用上的數據。監聽器方法不僅僅是在 JokesFeedTableViewController.swift
的 viewDidLoad 中會調用,只要數據庫中的 jokes 集合發生任何改變都會調用。
var jokes = [Joke]()
override func viewDidLoad() {
super.viewDidLoad()
// observeEventType is called whenever anything changes in the Firebase - new Jokes or Votes.
// It's also called here in viewDidLoad().
// It's always listening.
DataService.dataService.JOKE_REF.observeEventType(.Value,withBlock: { snapshot in
// The snapshot is a current look at our jokes data.
print(snapshot.value)
self.jokes = []
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
for snap in snapshots {
// Make our jokes array for the tableView.
if let postDictionary = snap.value as? Dictionary
測試 App
接下來測試這個 App。新建一個用戶,并發表幾則小笑話。你可以對這些笑話進行點讚。在 Firebase 的 dashboard 中,你可以看到在數據庫中,相應的對象會被創建。
結束語
好了!這個小 App 雖然有點搞笑,但用戶會更看中它的響應式特性。我們也由此獲得了使用 Firebase 的寶貴經驗。
項目完成后的代碼在此處下載。
對 iOS 開發者來說,使用 Firebase 能讓我們衍生出無限可能。FirebaseJokes 項目是一個很好的開始,但也僅僅是開始。
嘗試一下關於用戶驗證的其他選項,為 FirebaseJokes 添加新的功能,比如實時聊天功能,只有你想不到的,沒有你做不到的。
這裡再說一下圖片的問題:Firebase 會有一定的存儲限制,因此最好將圖片放在別的地方。對於文字類的 App,存儲完全不是問題,但對於大文件,我們最好採用另外的方案。
此時不做更待何時,趕緊將 Firebase 集成到你其它的項目中去吧!