今回のエンジニアブログを担当する村田です。
今回は、とあるイベントで、すぐに試したい!と一目惚れした「Firebase」を触ってみたいと思います。
Firebaseとは?
Firebaseは、2014年 Googleに買収されたモバイルバックエンドサービス(mBaaS)です。
特徴としては、リアルタイムなデータ同期です。
あるデバイスから送信したデータが、即時に他のデバイスにも反映されます。
また、オフラインにも対応しており、
オフライン中に送信しようとしたデータは内部で保持され、オンライン時にFirebaseへ反映されます。
という訳で、Firebaseの特徴を体験しやすいチャットを作成したいと思います。
せっかくなので今回は、iOSアプリとしてチャットアプリを作成します。
<開発環境>
・Xcode 6.1.1
・使用言語: Swift
・Firebase iOS SDK v2.2.0
Firebaseの準備
Firebaseの公式サイトはこちらです。
Googleに買収されましたのでGoogle Cloud Platformにあるのかなと思いましたが、統合はされておりません。
画面左上の「SIGN UP」からアカウントを作成します。
制限はありますが「Hacker Plan」という無料のプランがあります。
検証であればこちらのプランで充分です。
サインインしたらDashboardでアプリを作成しましょう。
※ APP URLは、実装時に使うので覚えておきましょう。
作成したアプリを開いてみます。
このようにデータが可視化されているのが特徴的です。
今はまだデータがないので、空の状態ですが、、、
Firebaseの準備はこれで完了です。
SDKのダウンロード
SDKは「DOCS」ページにあります。
ちなみにiOSは、Objective-Cでの提供となります。
Swiftで実装する場合は、Bridging機能が必要となります。
アプリのUI作成
それでは、SwiftでiOSアプリを作っていきます。
iOSアプリ作成は今回メインではないので、すごくシンプルな画面構成とします。
今回作成するアプリの画面イメージです。
構成部品は、以下のとおりです(AutoLayoutを設定)
・チャット内容を表示する「Text View」
・自分の名前を表示する「Label」
・チャットを入力する「Text Field」
※ これらの部品をIBOutletでViewController.swiftに紐付けておくと、後の実装が楽です。
機能のとしては
・アプリ起動時に自動で自分の名前を「Label」に設定
・「Text Field」に入力し、ReturnでFirebaseへ反映
・Firebaseから取得したデータは「Text View」に反映
プロジェクト設定
プロジェクト設定からFirebaseを動かすのに必要なFrameworkとライブラリを追加します。
必要なものはこちらです。
libicucore.dylib |
libc++.dylib |
CFNetwork.framework |
Security.framework |
SystemConfiguration.framework |
ダウンロードしたSDKを解凍し、Firebase.Frameworkをプロジェクトに追加します。
必要なものを全て追加した設定がこちらです。
最後に、Build Settingsから"other linker flags"に
-ObjC |
SwiftからObjective-Cの呼び出し
プロジェクトに1つ、Objective-Cファイル(.m)を作成しましょう。(ファイル名は適当に)
作成時に、SwiftからObjective-Cを呼び出すのに必要なBridgingファイルの作成を聞かれますので作成します。
作成したObjective-Cファイル(.m)は必要ないので削除します。
そして、XXXX-Bridging-Header.hファイルに以下の一文を記述します。
#import <Firebase/Firebase.h>
これで、SwiftからFirebaseのクラス群を呼び出すことが可能です。
実装 - データ反映
まずは、TextFieldでreturnすると入力した内容をFirebaseに反映してみます。
class ViewController: UIViewController, UITextFieldDelegate { var myRootRef: Firebase! @IBOutlet weak var textView: UITextView! @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var inputField: UITextField! override func viewDidLoad() { super.viewDidLoad() // Nameにラベルを設定。"Guest"に0〜9をランダムで付与。 self.nameLabel.text = "Guest\(arc4random() % 10)" // Return時のイベントをハンドルするために登録 self.inputField.delegate = self // Firebaseへ接続 self.myRootRef = Firebase(url: "https://<your-firebase>.firebaseio.com/chat1/posts") } func textFieldShouldReturn(textField: UITextField) -> Bool { // キーボードを隠す textField.resignFirstResponder() // 入力内容を登録 self.myRootRef.childByAutoId().setValue([ "name": self.nameLabel.text, "data": textField.text ]) // TextFieldの入力内容をクリア textField.text = "" return true } }
Firebaseとの処理は、Firebaseクラスを利用します。
基本となる接続時のURLは、こちらです。
self.myRootRef = Firebase(url: "https://<your-firebase>.firebaseio.com/")
このように指定すると、どのようになるのか?
self.myRootRef = Firebase(url: "https://<your-firebase>.firebaseio.com/chat1/posts")
ここでデータを見てみます。
お分かりになりましたでしょうか?
URLに付与した"chat1"と"posts"分、階層化されていますね。
ちなみに、このように得られたmyRefは、先ほどのmyRootRefと同じところを指します。
self.myRootRef = Firebase(url: "https://<your-firebase>.firebaseio.com/") var myRef = self.myRootRef.childByAppendingPath("chat1/posts")
データの登録です。
今回、Firebaseへ送信するデータは、JSONで表現するとこのようなDictionaryを配列で持たせます。
{ "name" : "<名前>", "data" : "<入力した内容>" }
これを実現したのが、このコードです。
// 入力内容を登録 self.myRootRef.childByAutoId().setValue([ "name": self.nameLabel.text, "data": textField.text ])
childByAutoIdメソッドは、IDを自動生成するメソッドです。
とあるパスの下に順次データを追加する場合は、このメソッドを使うと便利です。
childByAutoIdメソッドはFirebaseクラスを返しますので、setValueメソッドでデータを設定(Firebaseへの送信)します。
では、実行してデータを見てみます。
ちゃんと入力した順にデータが格納されていますね。
実装 - データ取得
Firebaseからデータを取得し、Text Viewに反映してみます。
override func viewDidLoad() { super.viewDidLoad() // Nameにラベルを設定 self.nameLabel.text = "Guest\(arc4random() % 10)" // Return時のイベントをハンドルするために登録 self.inputField.delegate = self // Firebaseへ接続 self.myRootRef = Firebase(url: "https://blog-sample.firebaseio.com/chat1/posts") // Child追加時のイベントハンドラ self.myRootRef.observeEventType(.ChildAdded, withBlock: { snapshot in if let name = snapshot.value.objectForKey("name") as? String { if let data = snapshot.value.objectForKey("data") as? String { self.textView.text = "\(self.textView.text)\n\(name) -> \(data)" } } }) // 接続直後に呼び出されるイベントハンドラ self.myRootRef.observeEventType(.Value, withBlock: { snapshot in if let isNull = snapshot.value as? NSNull { return } if let name = snapshot.value.objectForKey("name") as? String { if let data = snapshot.value.objectForKey("data") as? String { self.textView.text = "\(self.textView.text)\n\(name) -> \(data)" } } }) }
Firebaseからのデータ取得は、observeEventTypeメソッドを使います。
第一引数は、イベントの種類です。
.ChildAddedは、Childが追加された時、ここではチャットの入力データが反映されたときです。
withBlockの引数は、FDataSnapshotクラスになります。ここから登録時のキーを使いデータを取得し、Text Viewへ反映してます。
.Valueは最初に呼ばれます。
.ChildAddedと異なり、こちらは、NSNullかどうかのチェックをしております。
指定したパスのデータが空でも.Valueの場合は呼ばれ、その場合はNSNullが格納されます。
これで完成です!
2つの端末で(またはシミュレーターを使ったりして)動かしてみると、リアルタイムなデータ同期が実感できるかと思います!
まとめ
現状、リアルタイムなデータ同期のサービスですが、これをイチから作ろうとすると大変です。
SDKはiOSだけでなく、Androidも提供されています。
また、REST APIも用意されているので、様々な言語から呼び出し利用できます。
更に、Firebaseがすごい!と思ったところは、サンプルプログラムの充実っぷりです。
今回Swiftで実装したので、たまたま「Swift Chat」というサンプルを見たのですが、
UIライブラリ「JSQMessagesViewController」をBridgingして利用し、LINEのようなチャットを作るこだわりはすごいです。
最後に余談ですが、if letのネストがありますが、Swift 1.2 ではまとめてを記述することができます。
ということで、Swift 1.2のリリースが楽しみです。