スマートデバイスアプリ開発のあれやこれや

GradleでWebアプリ

今更ながらJavaのビルドツール「Gradle」を触ってみました。
Mavenに比べるとビルドスクリプトが手軽に書けるのが良いですね!
(pom.xmlの構文エラーとの戦いを経験している特に)


今回はEclipseのGradleプロジェクト形式で作成したプロジェクトを
Tomcat上で動かすところまでの手順を整理してみます。
手元の環境はこんな感じです。

1. Gradle(STS)プロジェクトを作る

予めEclipseにGradle(STS)プラグインをインストールしておきましょう。
ヘルプのEclipse Marketplaceから入手可能です!

Java Quick start」でシンプルにプロジェクトを作成します。


f:id:cross-xross:20160319111038p:plain

2. Servlet-APIを入手する

今回は単純なJavaServletで動作を確認します。
build.gradeを編集し、Servlet-APIを依存ライブラリに追加します。

dependencies {
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'
    testCompile group: 'junit', name: 'junit', version: '4.+'
}


依存ライブラリを追加したらプロジェクトを選択して
「Refresh Dependencies」を実行すれば依存ライブラリが入手可能です。

3. プロジェクトをWTPに変換する

GradleプロジェクトのままではTomcatにデプロイできないので
WTPプロジェクトに変換したいと思います。

apply plugin: 'war'
apply plugin: 'eclipse-wtp'


「war」プラグインと「eclipse-wtpプラグインを追加で導入します。
上記の通りbuild.gradeファイルの先頭に記述を追加します。

導入後に

gradle eclipse


を実行すればWTPプロジェクトに変換されます。

4. Servletを実装する

ここは普通にServletを実装します。

@WebServlet(name="MyServlet", urlPatterns={"/test"})
public class MyServlet extends HttpServlet {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		resp.setContentType("text/html");
		PrintWriter writer = resp.getWriter();
		writer.append("<html><body><h1>Hoge</h1></body></html>");
		writer.flush();
	}
}

5. 実行する from Eclipse

実行→Run on Serverで起動します。
http://localhost:8080/test で画面が表示されます。

6. WARファイルを作成する

gradle buildコマンドを実行すればwarファイルも作成可能です。

MultipeerConnectivityで実現する近距離無線通信:序

 ふと思い立って,掲題のMultipeerConnectivity.frameworkを触ってみました。
近距離でのLINE風チャットアプリなんかが簡単に作れそうで楽しいAPIです……!
今回はMultipeerConnectivity.frameworkについて情報を整理してみました。

1. MultipeerConnectivityことはじめ

 MultipeerConnectivity.frameworkは,iOS7から使えるようになった近距離
無線通信を実現するためのフレームワークです。具体的には,Wi-FiとBlutoothを
使って,近くのiOSデバイスと通信するための基本機能をAPIで提供しています。
このフレームワークを使うと↓のようなアプリが作れます。

  • トランシーバーアプリ
  • チャットアプリ
  • 会議資料共有アプリ
  • ゲームの対戦機能

2. 機能のイメージ

 近距離通信するデバイスですが,大きく「ホスト」「クライアント」の2種類
存在しており,それぞれで微妙に役割が異なっています。「ホスト」は,1つの
通信グループ(同じサービス名を持つ通信)で1人だけ存在します。「ホスト」以外の
接続ユーザーは全て「クライアント」です。

f:id:cross-xross:20160122201308p:plain

 そして,クライアントは他のクライアントと直接通信できません。
クライアントが通信できるのは,ホスト1人だけ。そのため,接続ユーザー全員で
状況共有が必要な場合は,ホストが各クライアントに情報をブロードキャスト
する必要があります。


 また,1つの通信グループは最大でも8人までしか同時接続できないので
無尽蔵に大規模なグループは作れない……という点にも注意が必要です。


 MultipeerConnectivity.frameworkとは,言うなれば各接続ユーザに
「ホスト」「クライアント」の役割を与え,ユーザ間での通信を行うための
機能を提供するフレームワークといえます。

3. 各接続ユーザを識別する情報「Peer」

 次にPeerという概念について紹介します。Peerとは,接続ユーザーを
区別するための識別情報です。「ホスト」「クライアント」に関わらず
全てのユーザーがユニークなPeerを保持しています。

f:id:cross-xross:20160122204634p:plain

 MultipeerConnectivityの世界では,Peerを表現するためのクラスとして
MCPeerIDというクラスが用意されています。

4. 交信機能を提供する「Session」

 続いて,ユーザ間のメッセージングを実現するSessionについて説明します。
Peerと同じくSessionも各ユーザが1つ持っています。そして,自分が保持する
Sessionを使って他のユーザと交信(データ送信,データ受信)を行います。

f:id:cross-xross:20160122205812p:plain

 ちなみに,Sessionは内部属性として,前述のPeer,つまり誰がそのSession
の持ち主か?という情報と,サービス名という情報を持っています。サービス
名というのは,通信グループの識別名称と理解してください。Session同士が
交信を行うためには,サービス名が同じである,という前提条件があるので
注意してください。


f:id:cross-xross:20160122213448p:plain


 MultipeerConnectivityの世界では,Sessionを表現するためのクラスとして
MCSessionというクラスが用意されています。

5. 存在を主張する「Advertiser」

 ここまで,ユーザの識別情報を保持する「Peer」と,ユーザ間での交信を
担う「Session」について説明しました。では,実際のユーザ間の接続は
いったいどういう流れで行われるのでしょうか?そこには,「Advertiser」
という要素が絡みます。Advertiserとは,サービスの存在を主張する機能です。


 ユーザ間で交信をするにしても,最初はまずサービスへの接続が必要です。
そのためには,他のユーザに対してホストが,サービスの存在を主張する
必要があります。その存在主張をAdvertiserを通して行うのです。


f:id:cross-xross:20160122220550p:plain


MultipeerConnectivityの世界では,MCAdvertiserAssistantクラスが
Advertiserの機能を提供しています。

6. 次回予告

 今回は概念的な話で近距離通信を説明したので,次回は同じ話を実装レベル
で解説してみたいと思います。

今年個人的に注力したい技術

 あけましておめでとうございます。
今年も月に最低1件の技術ネタ投稿を目標にやっていきたいです。
さて,今年の抱負...ではないですが,今年は未経験の以下の技術について
調査しながら力を付けていきたいです。

 ・node.js
 ・Golang
 ・データベース一般知識

 去年初めて良かったことは継続し,反省すべきことは反省しで
今年も頑張っていきたいと思っています。
今後共よろしくお願いします。

Spring BootアプリをTomcatにデプロイする

 niwaka.hateblo.jp

 前回のSpringBoot記事から暫く空きました...。
気がつけばバージョンも1.3.0.Releaseまで到達しており
そのうち時間を取って知識を更新しなくてはと思っています。

 とはいえ、今回はまた別の話です。
SpringBootは組み込みTomcatの機能を備えているので
単体実行(APサーバ不要で)可能な.jarファイルが作成可能です。

 「こんなアプリ作ったんだけどどう?」みたいな感じで
誰かに簡単にアプリを試してもらうには便利な機能ですが
時たま「やっぱり.warファイル作りたい」というシーンがあります。

 今回はSpringBootアプリを.warファイルにまとめて
Tomcatにデプロイする方法を紹介します。

1. pom.xmlファイルを修正する

 .warファイル作成にあたって、追加で必要な
ライブラリがあるため、pom.xmlファイルを修正していきます。

  <modelVersion>4.0.0</modelVersion>
  <groupId>jp.co.cross_xross.web</groupId>
  <artifactId>SpringBootSample</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <!-- 変更1. パッケージを「war」に設定  -->
  <packaging>war</packaging>

  <!-- 中略 -->
    <!-- 変更2. jarの依存関係を追加する -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <scope>provided</scope>
    </dependency>

2. SpringBootServletInitializerのサブクラスを作成する

 そのままでは、.warファイルを作成しても
web.xmlファイルが存在しないことから正常に起動できません。
そのため、Servlet起動時の設定情報を付与する必要があります。

 mainメソッドを持ったクラスに以下の変更を加えます。

  • SpringBootServletInitializerを継承する
  • configureメソッドをオーバーライドする

 

public class SampleController extends SpringBootServletInitializer {
// 中略

    public static void main(String args[]) {
	SpringApplication.run(SampleController.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(SampleController.class);
    }

3. ビルドする

 あとは簡単。「mvn package」を実行すれば
targetディレクトリ配下に.warファイルが作成されます。
Tomcatのwebappディレクトリに.warファイルに置いて起動確認しましょう。


 ちなみにURLは「http://ホスト名/アプリ名/*」という形式になります。
単体実行可能な.jarファイルと形式が変わるので注意しましょう......。

4. トラブルシューティング的な話

 「.warファイルをデプロイしたんだけど、404エラーが...」
という人のための話を補足でちょっと書いておこうと思います。


 Tomcatが動いているJavaのバージョンと、Mavenビルド時に
設定したJavaのバージョンが異なると404エラーになることがあります。
(自分はMaven: 1.6。Tomcat:1.8だったのでハマりました......)


 Mavenで使うJavaのバージョン指定は以下の方法で出来ます。

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>  


 Tomcatは、JAVA_HOMEの設定を使うので確認しましょう。

RxSwiftでUITableViewのバインド処理

niwaka.hateblo.jp

 ↑の記事で、UITableViewとのバインド時はセルが再利用されるので
Disposableをdispose関数で無効にする(キリッ)と言ってましたが

 もっと良い方法がありました......。

1. RxSwiftはUITableViewのバインド機能を提供している

 そうなんです。自前でちまちまバインド解除とか不要だったんです。
UITableViewやUICollectionView向けに簡単バインド機能を提供しているのです。

 どのくらい便利かというと......。

  • UITableViewDelegateの実装が不要
  • UITableViewDataSourceの実装が不要


 上記のプロトコルの実装=UITableViewの利用というイメージが
あったので、初めてこの機能を使ったときは半信半疑でした。
でも、RxSwiftを使うと動くのです......。

2. UITableViewとViewModelのバインドを定義する

 UITableViewに表示したいデータを保持するViewModelを定義します。

class ViewModel {
    /**
     仮面ライダーの名前を保持する配列
     */
    var riders = Variable<[String]>([])

    init() {
        riders.value.append("仮面ライダー龍騎")
        riders.value.append("仮面ライダーカブト")
        riders.value.append("仮面ライダーキバ")
        riders.value.append("仮面ライダーアギト")
        riders.value.append("仮面ライダーW")
        riders.value.append("仮面ライダーディケイド")
        riders.value.append("仮面ライダーオーズ")
        riders.value.append("仮面ライダー電王")
        riders.value.append("仮面ライダーウィザード")
    }
}


 そして、バインド対象のUITableViewを保持するController。

import UIKit
import RxSwift
import RxCocoa
import RxBlocking

class ViewController: UIViewController {

    /**
     TableView
     */
    @IBOutlet weak var list: UITableView!
    
    /**
     ViewModel
     */
    let model = ViewModel()
    
    /**
     Bag
     */
    let bag   = DisposeBag()
    
    /**
     Storyboardバインド後処理
     */
    override func viewDidLoad() {
        super.viewDidLoad()
        bindViewAndModel()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    /**
     データバインド処理
     */
    private func bindViewAndModel() {
        // UITableViewとViewModelのバインド
        model.riders.bindTo(list.rx_itemsWithCellIdentifier("CellIdentifier")) { _, riderName, cell -> Void in
            cell.textLabel?.text = riderName
        }.addDisposableTo(bag)
    }

}

3. 実行結果

 見事バインドされております。

f:id:cross-xross:20151117212630p:plain

4. 補足情報

 UITableViewのセル押下時のイベントハンドリング用の関数や
セル削除の関数もRxSwiftが提供しているので基本的に前述のProtocolの
実装は不要......ですが、複雑なテーブル作成時にはRxSwiftではサポートが
難しい場合もあるような気がします。


 個人的には、SectionHeader対応とかが必要になってきたら
結局はUITableViewDataSourceの実装とかが必要なんだろうな、と。


 ただ、単純なテーブル作成であればすごく便利な機能だと思います。

RxSwiftでデータバインド -補足編-

 このエントリは、Qiitaに投稿した記事の補足です。

qiita.com

 上記の記事では、RxSwiftを使った双方向データバインドの
実現方法をまとめていますが、データバインドの解除方法についても
少し整理して書いておきたいと思います。

1. データバインドを解除しないと困るケース

 自分が遭遇した困るケースは、UITableViewとのデータバインド......。
UITableViewの中で扱われるUITableViewCellのインスタンスは使い回しされます。
例えば、7行表示のテーブルでは、Cellのインスタンスは7つだけ作成され
画面スクロール時も当該インスタンスのプロパティを更新する動きになります。


 そのため、CellとViewModelのインスタンス(Array)をバインドすると
ViewModelとControllerの1対1のバインド関係が崩壊してしまい
ViewModelを更新したタイミングで意図しないCellが書き換えられたりします。
1行目のCellとバインドされたViewModelを更新すると1行目のCellを流用した
○行目のCellがなぜか更新されてしまう......なんて感じですね。


 対策として、非表示になった行については
データバインドを解除してやることでViewModelとControllerのバインド
関係の整合性を保ちます。この方法を採ることで、データバインドされている
Cellは画面に表示されている部分だけ......という状況を作っています。

2. データバインド解除する方法

 簡単です。
データバインドの処理を書いたときのことを思い出します。

/**
 データバインド設定
 */
private func bindViewAndModel() {
    textField.rx_text.subscribeNext { [unowned self] text in
        self.viewModel.text = text
    }.addDisposableTo(bag)
}   


 上記コードのsubscribeNext関数はDisposable型インスタンスを返却します。
(返却されたインスタンスを即座にDisposableBagに突っ込んでますが......)


 このDisposable型インスタンスとは何か?という点がポイントになります。
Disposableは”バインドしたよ!”という保証書だと理解すると簡単です。
RxSwiftではDisposable(保証書)が有効である限りデータバインドを行います。
逆にいうとDisposableを失効させればバインドを解除できます。
Disposableクラスは、dispose関数を提供しているので実行しましょう。

/**
 データバインド設定
 */
private func bindViewAndModel() {
    let disposable = textField.rx_text.subscribeNext { [unowned self] text in
        self.viewModel.text = text
    }
    // データバインド解除
    disposable.dispose()
}   


 ね?簡単でしょう。

3. DisposeBagとは何か?

 Disposableが何か分かると、DisposeBagの役目も分かります。

class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    let viewModel = ViewModel()
    let bag       = DisposeBag()
    // 中略
}


 Disposableインスタンスを保持するのがDisposeBagです。
その役目は即ち


 ”deinitのタイミングで保持している全Disposableを失効させる


ことです。
上記のコードで言うと
画面破棄のタイミングで自動的にデータバインドが解除されます。

 明示的にデータバインド解除を行わない限りは
DisposeBagを活用するのがベストプラクティスになると思います。
(解除を忘れると怖いですし......)

SwiftBondでデータバインドを実現する

 Modelを実行した後、画面更新処理をせこせこ実装するのが面倒...。
”更新処理を意識せずにiOSアプリが書けんものか”と思って色々調べてみると
SwiftBondというOSSライブラリを発見しました。
 github.com

 コイツを上手く使ってやると
ViewControllerとViewModel間でデータバインドが簡単に実現できます。
(ViewModelは必須ではないですが、Modelの実行結果の変換処理が
 ViewControllerから切り離せるのでViewModelがあった方が良いですね)

1. SwiftBondを導入する

 CocoaPodsを使って簡単インストールです。

use_frameworks!
pod 'Bond', '~> 4.0'

2. ViewModelを作成する

 ViewModelクラスを作成します。
今回はUITextFieldにバインドするString型変数1つを持ったクラスを作ります。

import Foundation
import Bond

class ViewModel {
    /**
     UITextFieldに対応する変数
     */
    var text = Observable<String?>("初期値")
}


 ポイントはString型で初期化するのではなく
SwiftBondが提供するObservableというWrapperクラスを使う所です。
(Observableクラスがデータバインドの機能を下支えしています......!)

3. ViewControllerを作成する

 ViewControllerクラスを作成します。
StoryboardとバインドされたUITextField型変数と先ほど作成した
ViewModel型変数の2つをメンバ変数として持たせておきます。

import UIKit
import Bond

class ViewController: UIViewController {

    /**
     バインド対象のUITextField
     */
    @IBOutlet weak var inputBox: UITextField!
    
    /**
     ViewModel
     */
    let viewModel = ViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

4. ViewControllerとViewModelのBind処理を書き足す

 では、UITextFieldとViewModelをバインドしていきます。
今回はViewController側にバインド定義処理を実装したprivateな関数
「bindViewAndModel」を作りviewDidload関数から呼び出してみます。
(バインド定義が増えるとviewDidloadがゴミゴミするのでその対策)

    override func viewDidLoad() {
        super.viewDidLoad()
        bindViewAndModel()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    /**
     ViewとModelをバインドする
     */
    private func bindViewAndModel() {
        inputBox.bnd_text.bindTo(viewModel.text)
    }


 SwiftBondの提供する「bindTo」関数を使います。
これでUITextFieldの変更が自動でViewModelの変数textに伝搬します。
print関数でtextの中身を出力するような処理を書いて試してみてください。
ちなみに、「bindTo」関数は単方向のデータバインドを実現します。


 双方向のデータバインドを実現したい場合は
「bidirectionalBindTo」関数を使えばOKです。

    /**
     ViewとModelをバインドする
     */
    private func bindViewAndModel() {
        inputBox.bnd_text.bidirectionalBindTo(viewModel.text)
    }

補足. ViewModelの変数へのアクセス

 実際にバインドが成功しているか確認する場合の注意点です。
前述の通り、ViewModelの変数はObservable型として定義しているので
単純に print(viewModel.text) なんてしてもデータは正しく表示されません。
SwiftBondのvalue変数経由でUnwappingした値を取り出す必要があります。

    func pushCheckBtn() {
        print(viewModel.text.value!)
    }


 value変数はOptional型なのでさらにUnwrappすることも忘れずに!