【Swift】MQTTを使った簡易チャットアプリ

# 概要

SwiftでiOS向けのチャットアプリを作ってみましたので紹介してみます。
ソースコードも載せてあるので、チャットアプリを作ってみたい方や、
Swiftの勉強をしている方の参考になればと思います。

# どんなアプリ？

今回作成したチャットアプリは非常にシンプルなアプリです。
ルーム名とユーザー名を入力して入室すると、
同じルームにいる他の人とチャットできるだけのアプリです。
ですのでユーザー認証とかはとかは全くしてません。

## ソース

[github](https://github.com/fuku2014/swift-simple-matt-chat)

画面はこんな感じです

* ホーム画面
<img width="320" alt="home.png" src="https://qiita-image-store.s3.amazonaws.com/0/63169/9402fe83-55ad-415e-56fb-c4832d37d1a0.png">

* チャット画面（ライン風ですw）
<img width="320" alt="chat.png" src="https://qiita-image-store.s3.amazonaws.com/0/63169/5a3ee590-5496-1dc5-90c3-ca9e9bf6a5f7.png">


# 構成

クライアント側はSwiftです。
特にViewに関するフレームワークも使ってません。

通常チャットアプリを作ろうと思ったらiphoneアプリとは別に、サーバー側のアプリが必須になります。
しかし、今回はMQTTブローカーサービスを使うことで、サーバーの運用やサーバーサイドプログラミングを一切行わずにチャットアプリを作成していきます。

## MQTTとは？

簡単に説明すると軽量なメッセージングプロトコルです。
詳しくは検索すれば解説が出てくると思いますが、その軽量さからIoT等で注目されています。
Facebookのメッセンジャーでも使われているとの噂があるので、こいつを使えば簡単にチャットアプリを作れそうだと思ったので今回はMQTTを使ってみました。

## MQTTブローカーサービスとは？

MQTTのブローカーを提供してくれるサービスです。
MQTTのサーバーを立てるとなると、MosquittoやRabbitMQ等のブローカーを使って構築することになると思いますが、自分でサーバーを用意して構築しなくても、それらのブローカーを提供してくれるサービスがあるのです。

国内にも海外にもいくつかのサービスが出ているので、予算や好みに応じてどこかに登録します。
今回はニフティ株式会社が提供している[NIFTY Cloud MQTT（β）](http://cloud.nifty.com/service/mqtt.htm)を使ってみました。

今のところ完全無料で利用でき、ダッシュボードやヘルプが日本語なのでわかりやすくてオススメです。

※追記
ベータ版が終了して2016年4月以降は有料になるみたいなので注意です。

# MQTTブローカーサービスへの登録

[MQTTクイックスタート](http://cloud.nifty.com/guide/mqtt/)を参考に、[NIFTY Cloud MQTT（β）](http://cloud.nifty.com/service/mqtt.htm)への登録と、MQTTサーバーの作成を完了させます。

MQTTサーバーを作成したら、[ドメイン][MQTTポート][管理者ユーザー名][管理者パスワード]はメモっておきます。

# プロジェクト作成

さて、MQTTブローカーサービスへの登録が完了したらいよいよ実際にコードを書いていきます。
xcodeを開いて空のプロジェクトを作成しましょう。

## ライブラリのインストール

SwiftでMQTTを扱うためのライブラリをインストールします。
今回は[Moscapsule](https://github.com/flightonary/Moscapsule)を使いました。

インストールはCocoaPodsで行います。
プロジェクトと同じディレクトリに「Podfile」というファイルを用意し、以下のように編集します。

```Podfile
use_frameworks!

target 'MqttChat' do
  pod 'Moscapsule', :git => 'https://github.com/flightonary/Moscapsule.git'
  pod 'OpenSSL-Universal', '~> 1.0.1.l'
end
```

「Podfile」が作成できたら、ターミナルでPodfileを保存したディレクトリへ移動し、下記コマンドを実行してインストールします。

```
$ pod install
```

インストールが成功すると{プロジェクト名}.xcworkspaceというファイルができるので、そのファイルを開いてプログラムを書いていきます。

## ソースファイル構成

ソースファイルは以下のような構成になっております。

```
├── MqttChat
│   ├── Info.plist
│   ├── images
│   │   ├── bubbleMine.png
│   │   ├── bubbleSomeone.png
│   │   ├── icon.png
│   │   └── main_back.png
│   └── src
│       ├── AppDelegate.swift
│       ├── ChatModel.swift
│       ├── ChatViewCell.swift
│       ├── ChatViewController.swift
│       ├── Const.swift
│       └── HomeViewController.swift
├── MqttChat.xcodeproj
│   └── project.pbxproj
├── Podfile
└── Podfile.lock
```

StoryBoadやAutoレイアウトは使ってません。
ソースの全体は[github](https://github.com/fuku2014/swift-simple-matt-chat)にあげてますのでそちらを参照ください！

### Const

まずは定数ファイルを作成します。

```swift:Const.swift
import UIKit

class Const : NSObject {
    
    let MQTT_HOST : String = "{MQTT=HOST}"
    let MQTT_USER : String = "{MQTT=USER}"
    let MQTT_PW   : String = "{MQTT=PW}"
    let MQTT_PORT : Int32  = 0
}
```

定数ファイルではMQTTサーバーへの接続情報を記述しておきます。
MQTTブローカーサービスで作成したMQTTサーバーのドメイン、ユーザー名、パスワード、ポートを設定します。

### AppDelegate

続いてAppDelegateです。

```swift:AppDelegate.swift

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window              : UIWindow?
    var navigationController: UINavigationController?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool{
        let viewController: HomeViewController = HomeViewController()
        navigationController                   = UINavigationController(rootViewController: viewController)
        self.window                            = UIWindow(frame: UIScreen.mainScreen().bounds)
        self.window?.rootViewController        = navigationController
        self.window?.makeKeyAndVisible()
        return true
    }
}
```

Storyboardを使わないので、ここでUINavigationControllerと最初に表示されるHome画面となるHomeViewControllerを生成しています。

### HomeViewController

最初に表示されるHome画面となります。

この画面では特筆すべきことも無いですね。
ユーザー名とルーム名を入力するためのTextFieldとEnterボタンを生成しています。
Enterボタンが押されるとdoEnter()メソッドが実行されるので、そこでチャット画面へ遷移する処理を行っています。

### ChatViewController

チャット画面です。
このアプリのメイン画面なのでコードの量も多くなってます。
ポイントとしてはUITableviewを使っているところでしょうか？
チャット表示画面全体をUITableviewで表現し、メッセージ１つ１つをUITableViewセルで表示してます。
後術の「ChatModel」、「ChatViewCell」とセットで使います。

![chat.png](https://qiita-image-store.s3.amazonaws.com/0/63169/808a03d2-df92-b510-86ba-c2e765d5d6cb.png)

この画面では実際にMQTTサーバーと接続をするので、まずはライブラリをImportしています。

```
import Moscapsule
```

MQTTサーバーと接続する処理はviewDidAppear()で行っています。

## publish(送信側)

メッセージの送信はdoSubmit()メソッドで実施しています。
doSubmit()メソッドは「Send」ボタンが押された時に呼ばれ、TextFieldに入力されたメッセージと共に、ユーザーIDとユーザー名をMQTTサーバーに送信しています。

MQTTサーバーへ送信するメッセージのインターフェースとしてはxmlやjsonがあると思いますが、今回はシンプルにカンマ区切りにしてみました。

```swift:ChatViewController.swift
    func doSubmit(sender: UIButton) {
        let msg : String = [uuid, userName, inputTextField.text!].joinWithSeparator(",")
        
        mqttClient.publishString(msg, topic: roomName , qos: 2, retain: false)
        inputTextField.resignFirstResponder()
        inputTextField.text = ""
    }
```
「mqttClient.publishString」が送信している箇所になるのですが、この時にルーム名をtopicとして指定しています。
これにより、同じルーム名をトピックとしてsubscribeしているユーザーがメッセージを受信できます。

## subscribe(受信側)

メッセージの受信はviewDidAppear()指定しています。また、onMessageCallbackでsubscribeした時の処理を実施しています。onMessageCallbackではカンマ区切りで送られてくる文字列を分解し、showMessageメソッドを呼んでいます。

```swift:ChatViewController.swift
    func showMessage(data : [String]) {
        let subscribeUUID     : String = data[0]
        let subscribeUserName : String = data[1]
        var subscribeMessage  : String = ""
        
        for i in 2...data.count - 1 {
            subscribeMessage += data[i]
        }
        
        let type = subscribeUUID == uuid ? NSBubbleType.BubbleTypeMine : NSBubbleType.BubbleTypeSomeoneElse
        self.itemes.append(ChatModel(text: subscribeMessage, type: type, name: subscribeUserName))
        self.chatTableView.reloadData()
        
        let nos = chatTableView.numberOfSections
        let nor = chatTableView.numberOfRowsInSection(nos - 1)
        let lastPath:NSIndexPath = NSIndexPath(forRow:nor - 1, inSection:nos - 1)
        chatTableView.scrollToRowAtIndexPath( lastPath , atScrollPosition: .Bottom, animated: true)
    }
```

showMessage()メソッドでは、受信したメッセージをモデル化してUITableViewの要素に追加する処理をしています。
UITableViewをreloadData()することでデータを即時反映しメッセージを表示しています。

### ChatModel

メッセージ(TableViewのセル１つ１つ)をModel化したクラスになります。
後術のChatViewCellに表示するデータとして、名前とメッセージ、送信したのが自分か他人かどうかを管理しています。

### ChatViewCell

UITableViewCellを継承していてChatViewControllerでTableViewのカスタムセルとして指定しています。

```swift:ChatViewController.swift
chatTableView.registerClass(ChatViewCell.self, forCellReuseIdentifier: "tblBubbleCell")"tblBubbleCell")
```

Line風のチャット画面を表現するために、吹き出しの大きさをメッセージの長さによって変えたり、自分が送ったメッセージの場合は右側で緑の吹き出し、相手から送られたメッセージは左側に白い吹き出しで表示するというような処理を行っています。

# まとめ

以上がソースの構成になります。
いかがでしたでしょうか！
MQTTを使えばチャットアプリって意外に簡単に作れるんですね。

## 機能追加

このままではただのつまらないチャットアプリなので、多分AppStoreに登録しても誰からもダウンロードされないですよね（審査が通るかも怪しいw）
今回MQTTブローカーとして使用したニフティクラウドは[mbaas](http://mb.cloud.nifty.com/about.htm)も提供しているのでmbaasと連携してユーザー管理やアイコン画像の保存とかはやってみたいですね。
ソースは公開してあるので誰かがカスタマイズして面白いアプリをリリースしてくれることを期待してます。

## 小ネタ

MQTTのトピックには「/」や「#」のような特殊な文字が使えます。
例えば「#」はワイルドカードを表すのでルーム名に「#」を入力すると
全部のルームのメッセージを受信できたりするので面白いですw
何も知らない人からすると怖いですがw