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

Apacheのmod_rewriteモジュール

Apachemod_rewriteモジュールを使うことで
URLとそれに紐付くコンテンツの対応関係を書き換えることができます。

分かりやすくいうと、http://sample/test.htmlというページがあった場合に
mod_rewriteを使うと架空のURL http://sample/hoge で同じtest.htmlを表示する
なんてことが可能になるわけです。

消して〜♪ リライトして〜♪

1 mod_rewriteをロードする

Apachehttpd.confファイルを開きます。

LoadModule alias_module libexec/apache2/mod_alias.so
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
#LoadModule php5_module libexec/apache2/libphp5.so


mod_rewrite.soの先頭に付いている#を外します。

2 リライト対象のDirectory設定を変更する

リライトしたい要素をhttpd.confから探します。
今回はApache直下のDocumentsディレクトリ以下を対象にします。

<Directory "/Library/WebServer/Documents">
  <!-- この部分にリライトの設定を入れる -->
</Directory>


上記のコメント部分に以下の内容を追記します。

<Directory "/Library/WebServer/Documents">
  <!-- この部分にリライトの設定を入れる -->
  RewriteEngine On
  RewriteRule ^rewrite sample.html
</Directory>


”RewriteEngine On”は言わずもがな、リライトします!という宣言です。
"RewriteRule"の部分は「書き換え対象」と「書き換え先」を設定します。
上の例でいうとrewriteというURLが呼ばれたら内部的にはsample.htmlを呼ぶ...という意味です。

ここでは決め打ちで書いていますが、正規表現でも書けます。
(というか正規表現で書くことの方が多いと思います)

3. 忘れずApacheを再起動する

Apacheを再起動しましょー。

sudo apachectl restart

4. 実験する

http://localhost/rewriteをブラウザから呼んでみましょう。
sample.htmlのデータが表示されましたか?

Docker環境構築(Mac篇)

巷で流行ってるContainer型仮想環境 DockerをMacにインストールしてみます。
手元の環境は以下の通りです。

1. はじめに(環境概説)

DockerはContainer型仮想環境構築ツールです。
ホストOSとゲストOSでカーネルなどのリソースを共有することで
Hyper-Vのような完全仮想化環境よりも高速展開可能な仮想環境を実現します。


"カーネルの共有"という点がポイントでして
ホストOSとゲストOSがカーネル共有できるようなパターン......
つまりLinux環境ではないとDockerは使えないという制限があります。


Linux寄りの環境とはいえ、Macも例外ではありません。
そのため、Virtual Box上にLinuxのゲストOSを立て
そのゲストOSにDockerコンテナを管理させることで環境を実現します。
(言ってしまえば3層仮想化っぽい感じです)


3構造になるとパフォーマンス面が心配になりますが
VirtualBox上に立てるゲストOSとしてDocker特化型の
超軽量OSイメージである"boot2docker"を用いることで対策しています。

2. boot2dockerをインストールする

公式サイトからboot2dockerのイメージファイルを入手します。

github.com

インストーラを使うと楽なので、pkgファイル版をダウンロードします。
あとはインストーラーに従って次へ次へでインストールが完了します。
ちなみにVirtual Boxも同時にインストールされるので楽ちんです。

3. boot2dockerを初期化する

ターミナルを起動してboot2dockerを初期化します。
と、その前に念のためにインストール確認...

docker --version
# Docker version 1.7.0, build 0baf609


正常にバージョン情報が表示されてればOKです。


続いて、boot2dockerの初期化。

boot2docker init
#Latest release for github.com/boot2docker/boot2docker is v1.7.0
#Downloading boot2docker ISO image...


boot2dockerの動作に必要なイメージファイルがダウンロードされます。

そしてboot2dockerを起動します。

boot2docker up
# Waiting for VM and Docker daemon to start...
# ..........................ooooooooooooooooooooooooo


Dockerにアクセスするための環境変数の設定を促されますので
.bash_profileに設定を追加しましょう。

export DOCKER_HOST=tcp://XXX.XXX.XXX.XXX:2376
export DOCKER_CERT_PATH=/Users/UserName/.boot2docker/certs/boot2docker-vm
export DOCKER_TLS_VERIFY=1


追加したら,source .bash_profileで設定を反映します。

4. Dockerイメージを入手する

dockerコマンドを使ってContainerの素であるイメージを入手します。
試しに最新版のCentOSを入手してみます。

docker pull centos:latest


ダウンロードに成功すると、docker imagesで以下のように表示されるはず。

REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
centos              latest              7322fbe74aa5        2 weeks ago         172.2 MB

5. Dockerコンテナを起動する

ダウンロードしたイメージからコンテナを起動します。

docker run -t -i centos /bin/bash


コンテナが起動され、SSHでアクセスした状態になります。
この状態で必要なミドルウェアをインストールしてお好みの環境を作りましょう。

余談. アンインストール方法

boot2dockerの削除方法も書いておきます。

アンインストールスクリプトが公式で提供されているので
冒頭で書いたサイトからboot2dockerのzip版 or tar版も入手しておきます。


ターミナルからboot2dockerの仮想マシンを削除します。

boot2docker delete


その後、boot2dockerのzip版 or tar版に含まれるuninstall.shを実行しましょう。

あとは、/Applicationsからboot2dockerとVirtualBoxを削除すればOKです。

KIFでiOSアプリケーションのUIテストを自動化する

iOSネイティブアプリケーションのUIテストの自動化方法をまとめます。
最近のUIテスト自動化ツールとして代表的なものは↓です。

  • Calabash
  • KIF
  • MonkeyRunner


MonkeyRunnerは触ったことがないのでコメント出来ませんが
CalabashはRubyでコードを書く必要があって慣れないので
今回はKIFを使うパターンを紹介します。
※XCTestでのテストに慣れているならKIFの方がとっつき易いと思います。

検証環境は以下の通りです。

1. KIFインストール用のPodfileを作成する

CocoaPodsを使ってインストールしていきます。
公式サイトに習い、定義ファイル(Podfile)は以下のように書きます。

target 'テスト対象ターゲット', :exclusive => true do
  pod 'KIF', '~> 3.0', :configurations => ['Debug']
end


「テスト対象ターゲット」の部分はお手元の環境に合わせましょう。
作成したPodfileはテスト対象プロジェクトの直下に保存します。

2. KIFをインストールする

ターミナルを起動して、テスト対象プロジェクトの直下に移動します。
その状態でCocoaPodのインストールコマンドを実行します。

pod install


Xcodeワークスペースファイルが作成されていれば成功です。

3. テストクラスを作成する

作成されたXcodeワークスペースファイルをXcodeで開きます。
Xcodeプロジェクトは開かないように注意です!

テストクラスを作成していきます。

#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import <KIF/KIF.h>


@interface KifAppTests : KIFTestCase

@end

@implementation KifAppTests

- (void)setUp {
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testExample {
    // ここにテストコードを実装していくよ!
}


まずはKIFの必要クラスをインポートしていきます。
(KIF/KIF.hの部分ですね!)

続いて、テストクラスの継承元クラスを設定します。
KIF用のテストクラスになるので、継承元クラスは”KIFTestCase”を使います。

4. テストコードを実装する

KIFTestCaseクラスが提供するインスタンス(tester)を使って
画面操作を行うコードを実装していきます。
画面項目の同定にはAccesiblility Labelの情報を使います。
そのため、事前に操作対象の部品にラベルを設定しておいてください。


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


ボタンであれば、ラベルなしでもボタンの表示名ベースで特定可能です。


(1) 画面項目をタップする
画面項目をタップする場合のテストコードの書き方です。

- (void)testExample {
    [tester tapViewWithAccessibilityLabel:@"Button"];
}


引数で指定したAccessibility Labelを持つ項目をタップします。


(2) 画面項目に値を入力する
画面項目に値を入力する場合のテストコードの書き方です。

- (void)testExample {
    [tester enterText:@"hogehoge" intoViewWithAccessibilityLabel:@"input"];
}


第1引数に入力したいテキストを入力します。
第2引数にテキストを入力する項目のAccesibility Labelを指定します。

5. テストを実行する

XCTest実行時と同じようにテストターゲットを実行します。
実装した処理に従ってシミュレータの操作が自動化されていれば成功です。


実際に使ってみた感想です。


良いところ

  • XCTestと同じ感覚でUIテストの自動化コードを書ける
  • 導入が簡単である
  • 実機でもシミュレータでも実行できる


悪いところ

  • テストケース実行後に画面が初期状態に戻らない


予めリセット処理をアプリケーションごとに実装しておけば
良いだけなのですがちょっと面倒臭いような気がします。

Swiftで実現する非推奨(deprecated)APIの置換処理

来月のWWDC2015にて、次期iOS(iOS 9?)が発表されそうです。
新しいAPIが使えるようになり、楽しみが増える一方で使えなくなるAPIもあり
開発しているアプリの新バージョン対応に右往左往するシーズンです。

一般的に非推奨APIは以下の3種類に分類出来ます。


Objective-Cでの対応方法は確立しているので
今回は上記の3種類に対して、Swiftでどう対応していくのか整理します。

クラスが非推奨になった

Objective-C時代は、以下のような対応でした。

if ([DeprecatedClass class]) {
  // DeprecatedClassが存在する場合
} else {
  // DeprecatedClssが存在しない場合
  // 新バージョンでの代替APIを使う


Swiftでは以下のような処理を実装します。

if NSClassFromString("DeprecatedClass") {
  // DeprecatedClassが存在する場合
} else {
  // DeprecatedClssが存在しない場合
  // 新バージョンでの代替APIを使う
}

メソッドが非推奨になった

Objective-C時代は、以下のような対応でした。

if ([obj respondsToSelecor:@selector("deprecatedMethod")]) {
  // deprecatedMethodが存在する場合
} else {
  // deprecatedMethodが存在しない場合
  // 新バージョンでの代替APIを使う


Swiftでも同じですね!

if obj.respondsToSelector("deprecatedMethod") {
  // deprecatedMethodが存在する場合
} else {
  // deprecatedMethodが存在しない場合
  // 新バージョンでの代替APIを使う
}

プロトコルが非推奨になった

Objective-C時代は、以下のような対応でした。

if ([obj conformsToProtocol:@protocol("DeprecatedProtocol")]) {
  // DeprecatedProtocolが存在する場合
} else {
  // DeprecatedProtocolが存在しない場合
  // 新バージョンでの代替APIを使う


Swiftでも同じですね!

if obj.conformsToProtocol("DeprecatedProtocol") {
  // DeprecatedProtocolが存在する場合
} else {
  // DeprecatedProtocolが存在しない場合
  // 新バージョンでの代替APIを使う
}

楽なところから始めるObjective-CからSwiftへの移行

少し前から進めていたSwiftのお勉強の成果として
Objective-CからSwiftに移行するときのポイントをまとめてみます。


自分自身、そこまで習熟者ではないので
コンセプトとしては”楽なところ”から移行というテーマで
書いてみたいと思います。

Optionalでnilチェックを駆逐する

Swiftを始めて一番便利だと感じたのがOptional型です。
というか、非Optionalな変数がnilを拒絶する点が素晴らしい。

// 非Optional型の変数を定義します
var moji = "もじもじ君"
moji = nil // コンパイルエラー発生


こんな感じでXcodeがエラー扱いにしてくれます。

この特性がどこで発揮されるか!?といえば
その1つがメソッドの引数に関するnilチェック処理だと思います。
Objective-Cだと↓みたいなコードよく書きませんか?

- (NSString *)myMethod:(NSString *)arg  {
    // nilチェック
    if (arg != nil) {
        return nil;
    }
}


ただただ面倒くさい!
とはいえ、チェックがないと怖すぎる……。
Swiftの変数はデフォルトでnilを許容しないので
非Optionalで引数定義するだけでチェック処理が駆逐出来ます。

func myMethod(#arg:String) -> String {
    // さらばnilチェック
}

Genericsで型チェックを厳密に!

Genricsも意図しない型が代入されるリスクをなくせる点でメリットが大きいです。
クラス定義や関数定義でGenericsを活用する......という旨味もあるのですが
”楽なところ”からがコンセプトなので省略です。


じゃあ,どこで使うかといえば,配列とDictonaryです。
Objective-Cでは,配列やDictonaryに投入出来る変数型はid型なので
『この配列にはNSStringしか入れたくない!』ということを実現しようとすると
下のようなコードを書いて抑止するしかないです。

// 引数がNSStringクラスだったら
if ([args isKindOfClass:[NSString class]]) {
    [list addObject: args];
}


面倒です。とても面倒です。
Swiftでは定義するときに投入する型を指定します。

let list:Array<String> = Array<String>()


こんな感じで配列の中身はStringだよ!と指定してあげます。
上を書くのが面倒であれば下のような書き方でもOKです。

let list = [String]()


ちなみにディクショナリだと下のような感じです。

let map:Dictionary<String:MyClass> = Dictonary<String:MyClass>()


ひとまず、OptionalとGenericsの2点から始めると良いと思います。
今回のエントリには書いてませんが,.hファイルと.mファイルが
.swiftファイルにまとまるので,それだけでも楽になる気がします。

Tomcat 8.0系でのデータソース定義

Tomcat上で動くWebアプリでDB接続する必要があり
データソースの定義ってどうやるんだ?!と思って調べてみました。
ちなみにEclipseでDynamic Web Projectで開発したケース......です。


検証環境は以下の通り。

1. context.xmlを作成する

META-INFフォルダの直下にcontext.xmlという名前でファイルを作成します。
このファイルをTomcatが読み込んでコネクションプーリングを行います。
従ってデータベースの場所やら接続情報、JNDIのアクセス名などの情報を書き込みます。

<Context>
    <Resource name="jdbc/myapp" 
              auth="Container"
              type="javax.sql.DataSource" 
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/webapp"
              username="" 
              password="" />
</Context>


今回は設定していませんが
「maxActive(最大接続数)」「maxIdle(最低接続維持数)」「maxWait(タイムアウト時間)」
などの設定も付与可能です。

詳しくはTomcat公式をご覧くださいー。

2. web.xmlに参照情報を付与する

Tomcatが確保したコネクションをWebアプリから取得するため
web.xmlに参照情報を付与します。

  <resource-ref>
    <res-ref-name>jdbc/myapp</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
  </resource-ref>


注意点としては、各要素の設定情報は
「context.xml」でのネーミングと一致させることです。

3. 設定が上手くいってるか確かめてみる

実際にコードを書いて試してみます。
単純にDataSourceインスタンスが取れるかどうかで判定する例。

InitialContext context = new InitialContext();
DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/myapp");

if (ds != null) {
  System.out.println("データ取得できたー!");
}


さて、上手く取れてるでしょうか?

Spring BootでJSONデータを受け付けるWebAPIを作る

前回の記事の続きモノです。

niwaka.hateblo.jp

↑の記事で、POSTでリクエストパラメータを受け付けるWebAPIを
Spring Bootでサクッと作る方法をまとめてみましたが、今度は
リクエストをJSON形式で受け取る方法を整理してみます。
(前回の記事から続きで実装する体で進めます......!)

環境は以下の通りです。

1. Controllerクラスのメソッドをいじる(1)

前回、↓のようなControllerクラスを作りました。

@Controller
@EnableAutoConfiguration
public class SampleController {
	
	@RequestMapping("/service")
	@ResponseBody
	public List<String> top() {
		List<String> list = new ArrayList<String>();
		list.add("太宰");
		list.add("夏目");
		return list;
	}

このControllerクラスのtopメソッドを改造していきます。
いじるポイントは2つ。

  • @RequestMappingにconsumesを追加する
  • 引数でJSONデータをパース後にバインドするクラスを設定する


既存の@RequestMappingアノテーションに設定を追加します。
前回『メソッドに紐付いたURL』の設定を@RequestMappingで行うと書きましたが
実際のところはURLの紐付け設定だけでなく、受け取るデータ種別の設定も可能です。
今回は受け取るデータ種別としてJSONデータを受け取れるように設定します。
具体的には以下のような感じになります。

@Controller
@EnableAutoConfiguration
public class SampleController {
	
	@RequestMapping(value="/service",consumes=MediaType.APPLICATION_JSON_VALUE)
	@ResponseBody
	public List<String> top() {
		List<String> list = new ArrayList<String>();
		list.add("太宰");
		list.add("夏目");
		return list;
	}

前回書いていた『@RequestMapping("/service")』という部分は
省略せずに書くと『@RequestMapping(value="/service")』となります。
value以外にも設定は在って、今回はconsumesで受け取るデータ種別を設定しています。
(※前回、今回と省略してますが、戻るデータ種別を定義するproducesという項目も在ります)

2. JSONのデータを保持するクラスを作る

続いてリクエストで受け取ったJSONがバインドされるクラスを作ります。
Spring Boot(=Spring MVC)では、JSON形式のデータを受け付けた後、
対応するクラスにデータをバインドして渡してくれます。

今回は適当に↓のようなクラスを作りました。

public class SampleBean implements Serializable {

	private static final long serialVersionUID = 1L;
		
	private String name;

	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
	}	
}

3. Controllerクラスのメソッドをいじる(2)

またControllerクラスに戻ってきます。
ステップ2で作成したクラスとJSONマッピングするように再度
Controllerクラスを弄っていきます。

@Controller
@EnableAutoConfiguration
public class SampleController {
	
	@RequestMapping(value="/service",consumes=MediaType.APPLICATION_JSON_VALUE)
	@ResponseBody
	public List<String> top(@RequestBody SampleBean bean) {
		List<String> list = new ArrayList<String>();
		list.add("太宰");
		list.add("夏目");
		return list;
	}

topメソッドの引数に@RequestBodyアノテーション付きの引数を追加しました。
引数の型は先ほど作ったSampleBeanクラスです。
この設定を付与することでbeanインスタンスJSONデータがマッピングされます。
bean.getName()の結果をコンソール出力する処理を追記して試してみましょう。

4. 実際にリクエストしてみる

動いているか検証してみます。
以下のようなJavaScriptで検証できると思います。

    // 各フィールドから値を取得してJSONデータを作成
    var data = {
        "name": "hoge",
    };
    
    // 通信実行
    $.ajax({
        type:"post",                
        url:"http://localhost:8080/services",        
        data:JSON.stringify(data), 
        contentType: 'application/json', 
        dataType: "json",           
        success: function(json_data) {   
          // 成功時の処理
        }
    });