エンジニア

FirebaseでiOSアプリを作ってみよう!

投稿日:2015年3月3日 更新日:

今回のエンジニアブログを担当する村田です。

今回は、とあるイベントで、すぐに試したい!と一目惚れした「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でアプリを作成しましょう。
スクリーンショット 2015-02-27 17.05.13
※ APP URLは、実装時に使うので覚えておきましょう。

作成したアプリを開いてみます。
このようにデータが可視化されているのが特徴的です。
今はまだデータがないので、空の状態ですが、、、
スクリーンショット_2015-03-02_21_00_27

Firebaseの準備はこれで完了です。

SDKのダウンロード

SDKは「DOCS」ページにあります。
ちなみにiOSは、Objective-Cでの提供となります。
Swiftで実装する場合は、Bridging機能が必要となります。

アプリのUI作成

それでは、SwiftでiOSアプリを作っていきます。
iOSアプリ作成は今回メインではないので、すごくシンプルな画面構成とします。

今回作成するアプリの画面イメージです。
スクリーンショット_2015-03-02_21_10_01
構成部品は、以下のとおりです(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をプロジェクトに追加します。
必要なものを全て追加した設定がこちらです。
スクリーンショット 2015-03-02 21.38.41

最後に、Build Settingsから"other linker flags"に

-ObjC

を追加します。
スクリーンショット_2015-03-02_21_33_15

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")

ここでデータを見てみます。
スクリーンショット_2015-03-02_22_03_21
お分かりになりましたでしょうか?
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への送信)します。

では、実行してデータを見てみます。
スクリーンショット 2015-03-02 22.28.22
ちゃんと入力した順にデータが格納されていますね。

実装 - データ取得

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のリリースが楽しみです。

採用情報

ワンダープラネットでは、一緒に働く仲間を幅広い職種で募集しております。

-エンジニア
-,

© WonderPlanet Inc.