かみやんの技術者ブログ

主にプログラムの話です

Webシステムのフロントエンド高速化で最初にやるべきこと

前回のエントリーで、Dartの次は、TypeScriptを検証する。と書いたけど、なぜか自分のPCでは、VisualStudio for WebにTypeScriptのプラグインがインストールできなかったので、TypeScriptを使うことを諦めました。コマンドラインコンパイルはできたけど、それでは型付け言語のメリットであるIDEによる補完や参照検索やリネームリファクタリングが効かないので。ちなみにプログラマのPCではあっさりインストールできたとのこと。がっくり。

というわけで、Dartを実戦投入することを決定してDartで開発をしています。

フロントエンド高速化のExpiresヘッダ

さて、今日の本題。Webシステムのフロントエンド高速化のお話です。Webシステムの速度の大きなボトルネックとしてDB負荷がありますが、ブラウザ側のレンダリングを高速化する話としてフロントエンド高速化があります。一般には、

  • hmtl, css, JavaScriptなどのテキストデータはgzip圧縮転送しましょう
  • htmlからJavaScriptのインクルードは、</body>の直前に書きましょう
  • cssからcssをインポートする@importは使わないようにしましょう
  • htmlからcssのインクルードは、<head>タグの中などhtmlの先頭近くに置きましょう
  • JavaScriptcssはhtmlの中にインラインで極力書かずに別ファイルにしてブラウザのキャッシュを効かせましょう
  • 静的コンテンツにはExpiresヘッダをつけてブラウザのキャッシュを効かせましょう

などがあります。が、もっとも効果的なのは最後のExpiresヘッダの追加ではないか。と。特にスマホなどのモバイル端末からのアクセスなど回線が遅い場合には、絶大な効果を発揮するはず。と。
とにかく俺は高速化が大好きなのだ!!

Expiresヘッダとは

HTTPサーバがレスポンスを返すときに、

Expires: Sun, 17 Feb 2014 22:28:04 JST

のような日付を返すと、その時刻まではこのコンテントは変更しないよ。とブラウザに伝えるもので、上記だと「2014/2/17」まで変更しないよ。と伝えています。つまり、今から1年後を指定しています。
通常ブラウザは、Expiresの期限が来るまではコンテントをストレージにキャッシュし、二度とサーバに同じURLをリクエストしません(キャッシュがいっぱいになったので消すとかがなければ)。
ブラウザがHTTPリクエストを送らなくなるので、通信時間は減り、サーバが応対する必要がなく、AWSのようにトラフィック料がかかるサーバ費用も抑えられます。
という訳でExpiresヘッダの威力は絶大だ!

Apacheの場合

mod_expiresを追加して、

ExpiresDefault "access plus 1 years"

を書くだけでOKです。
MIME-Type別に指定する場合は、

ExpiresByType text/html "access plus 1 years"

のようにExpiresByTypeを利用すればOKです。
ただ、MIME-Typeだけでは静的コンテンツと動的コンテンツの区別がつかないので、僕は、JavaServlet側で対応しました(PHPとかRubyとかでも同様でしょう)。

コンテントの更新に対応

Expiresヘッダを追加した場合、二度とブラウザが同じURLをアクセスして来なくなる可能性が高いため、静的コンテントを更新したときに問題になります。その場合は、URLを変えるしかありません。通常はファイルのバージョン番号を入れるか最終更新日を入れるかです。

<img src="hello.png">

と書く代わりに

<img src="hello.png?20130217">

などのように書くということです。上記の20130217は、当然最終更新日が「2013/02/17」という意味です。hello.pngを更新する度にjspの?20130217を修正していくのは大変すぎるので、

<img src=<%=Resource.get("hello.png")%> >

というようにResource.get()を通すようにしました。Resourceクラスは、自作のクラスで静的コンテントのURLを入力しその最終更新日を「{URL}?lm=YYYYMMddHHmmss」形式で返すものです。
Resource#get()メソッドが最終更新日を得る方法ですが、デバッグモードとリリースモードの2つを用意しました。デバッグモードでは、リクエストされたURLが存在するローカルのファイルの最終更新日をみてそれを返します。
リリースモードでは、事前にバッチファイルで作成したlastModified.propertiesファイルを参照し高速にレスポンスするものです。lastModified.propertiesファイルは、キーがURL、値がYYYYMMddHHmmss形式の更新日になっています。本番環境にデプロイするときにバッチを起動してlastModified.propertiesを更新します。lastModified.propertiesファイルは更新されるとTomcatが感知して自動リロードしてくれます。それ以外のときのpropertiesファイルの参照ではJavaのライブラリ内でpropertiesファイルをキャッシュしてくれていてメモリアクセスしかないので高速です。

拡張子

ちなみに僕が管理しているプロジェクトでは、静的コンテンツは*.css, *.js, *.pngなどのように通常の拡張子を使い、動的コンテンツは実態がServletでもJSPでもレスポンスがtext/htmlでもimage/pngでもtext/cssでもtext/javascriptでも拡張子を*.jspとしている。
CDNコンテンツデリバリネットワーク)などのキャッシュを効かせる時にも動的か静的かで分けたほうがよいので。

ベンチマーク

高速化を組み込んだ結果、

内容 高速化前 高速化後
3Gでのページが表示されるまでの時間 9秒 1秒
LANでのページが表示されるまでの時間 700ms 200ms
転送量 1Mbyte 70kbyte

という訳で劇的に高速化されました。ちなみに転送量70kbyteというのは静的コンテンツがすべてキャッシュにヒットした場合(つまり初めてサイトに訪れたときではなく2度目以降)です。転送量70kbyteというのは、すべてjspがレスポンスしたHTMLで、ここをgzip圧縮すると9kbyteに減りました。画像レスポンスはゼロに。
つまりgzip圧縮をして70kbyteが9kbyteになっても1Mbyteの画像が減らなければ効果なし。画像レスポンスを0にした上でgzip圧縮は効果あり。と。
あと転送量の影響はすくないもののcssのキャッシュヒットもページレンダリングに大きく影響します。なにせcssファイルが届かなければほぼレンダリングは進まないので。
高速化!高速化!高速化!

Resource.get()のソース

Resource.get()の実体は、下記のような感じ。

public class Resource {
    /**
     * URLを入力してYYYYMMddHHmmss形式の日付を返す<br>
     * 例) 入力 css/base.css 出力 201302101415
     * 
     * @param uri
     * @return みつからないときは、""
     */
    public static String getDate(String uri) {
        ResourceBundle settings = ResourceBundle.getBundle("settings");
        if ("true".equalsIgnoreCase(settings.getString("debug.mode")) == false) {// リリースモードのとき
            ResourceBundle lm = ResourceBundle.getBundle("lastModified");
            if (lm.containsKey(uri)) {
                return lm.getString(uri);
            } else {
                ErrorUtil.sendInfo(uri
                        + " is not found in lastModified.properties.");
                return "";
            }
        } else {// デバッグモードのとき
            try {
                SimpleDateFormat sdf = new SimpleDateFormat("YYYYMMddHHmmss");
                String path = settings.getString("servlet.context") + '/' + uri;
                Path p = FileSystems.getDefault().getPath(path);
                FileTime t = Files.getLastModifiedTime(p);
                Date d = new Date(t.toMillis());
                return sdf.format(d);
            } catch (IOException e) {
                ErrorUtil.sendInfo(uri
                        + " is not found at Resource#getDate() in debug mode.");
                return "";
            }
        }
    }

    /**
     * URLを入力して、 "{url}?YYYYMMddHHmmss" を返す(ダブルクォートあり版)<br>
     * 例) 入力 css/base.css 出力 "css/base.css?lm=201302101415"
     * 
     * @param uri
     * @return
     */
    public static String get(String uri) {
        String date = getDate(uri);
        return "\"" + (date.length() > 0 ? uri + "?lm=" + date : uri) + "\"";
    }
}

StaticResourceServlet.java

〜.cssや〜.pngや〜.jsの静的コンテンツをレスポンスするServletのソースは下記になります。

@WebServlet(urlPatterns = { "*.css", "*.js", "*.png", "*.jpg", "*.jpeg", "*.gif" })
public class StaticResourceServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        try {
            doNext(request, response);
        } catch (Exception e) {
            ErrorUtil.sendError(e);
            throw new ServletException();
        }
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        try {
            doNext(request, response);
        } catch (Exception e) {
            ErrorUtil.sendError(e);
            throw new ServletException();
        }
    }

    /**
     * メイン処理
     * 
     * @param request
     * @param response
     * @throws Exception
     */
    private void doNext(HttpServletRequest req, HttpServletResponse res)
            throws Exception {
        String uri = req.getRequestURI();
        String contextPath = req.getServletContext().getContextPath() + '/';
        String fname = uri.substring(contextPath.length());
        String realPath = req.getServletContext().getRealPath("")
                + File.separator + fname;
        if (uri.indexOf("WEB-INF") != -1) {// WEB-INFが含まれているとき
            throw new Exception("URLにWEB-INFが含まれています");
        }
        if (uri.indexOf("..") != -1) {// ..が含まれているとき
            throw new Exception("URLに..が含まれています");
        }
        if (uri.endsWith(".js")) {// JavaScriptのとき
            res.setCharacterEncoding("UTF-8");
            res.setContentType("text/javascript");
        } else if (uri.endsWith(".css")) {// CSSのとき
            res.setCharacterEncoding("UTF-8");
            res.setContentType("text/css");
        } else if (uri.endsWith(".png")) {// pngのとき
            res.setContentType("image/png");
        } else if (uri.endsWith(".jpg") || uri.endsWith(".jpeg")) {// jpegのとき
            res.setContentType("image/jpeg");
        } else if (uri.endsWith(".gif")) {// gifのとき
            res.setContentType("image/gif");
        } else {
            throw new Exception("サポートしていない拡張子");
        }
        ResponseUtil.setExpiresHeader(res);// Expiresヘッダ
        if (req.getParameter("lm") == null) {// lmパラメータがないとき
            ErrorUtil
                    .sendInfo(uri
                            + "のリクエストでlmパラメータがついていません。Expiresヘッダ高速化に必要です。 at StaticResourceServlet");
        }
        ByteArrayOutputStream baos = null;
        BufferedInputStream bis = null;
        ByteArrayInputStream bais = null;
        try {
            baos = new ByteArrayOutputStream();
            bis = new BufferedInputStream(new FileInputStream(realPath));
            // ファイルからメモリへコピー
            byte[] buf = new byte[4096];
            int size;
            while ((size = bis.read(buf)) >= 0) {
                baos.write(buf, 0, size);
            }
            // メモリからレスポンスへ出力
            res.setContentLength(baos.size());// Content-Lengthヘッダ
            bais = new ByteArrayInputStream(baos.toByteArray());
            ServletOutputStream out = res.getOutputStream();
            while ((size = bais.read(buf)) >= 0) {
                out.write(buf, 0, size);
            }
            out.flush();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (Exception e) {
                }
            }
            if (baos != null) {
                try {
                    baos.close();
                } catch (Exception e) {
                }
            }
            if (bais != null) {
                try {
                    bais.close();
                } catch (Exception e) {
                }
            }
        }
    }
}

その他のパターン

ファイルシステム上にそのままファイルが置かれている静的コンテンツ以外に、DBに静的コンテンツがある場合は、DBのフィールドにLastUpdateなどのTIMESTAMP型を用意して、Resource.get()で取れるようにしています。
あと、JavaScriptから静的コンテンツの最終更新日を取る部分では、JavaScript版のResource.get()相当のメソッド(内容はURLと更新日の連想配列データを埋め込んだもの)をサーバがレスポンスするようにしています。

最初にやりましょう!

この仕組みですが、プロジェクトの開発終了直前でやるはめになりましたが、Resource.get()に書き換え箇所が膨大だったため、死にそうでした。
死にそうでもやる価値はあるので皆さんやった方がよいですが、できればプロジェクトの開始時に仕組みを導入しておいた方が賢明です。
高速化!高速化!高速化!

――
アプリ受託開発、Webシステム受託開発も行っています。以下自社製品。
URL:ibisMail Freeのダウンロード、レビューはこちら
URL:ibisMail for iPhoneのダウンロード、レビューはこちら
URL:ibisMail for iPadのダウンロード、レビューはこちら
URL:ibisPaintのダウンロード、レビューはこちら サイトは、ibispaint.com
ご意見ご要望はTwitterから: @kamiyan

GoogleのDartを使ってみた

JavaScriptをたくさん使うプロジェクトの現状

JavaScriptをガンガン書かないといけないWebアプリは、ビジネスロジックをJavaServletで書いて(他の言語でもいいけど)、JSPへ転送して(他の言語でもテンプレートとか使うと思うけど)、JavaScriptもクライアントに送って、クライアントでは、HTML+CSSでビジュアルがレイアウトされて、JavaScriptがビジュアルを動かしたり、AJAXでまたサーバにリクエストしたり、AJAXのレスポンスでまたビジュアルが変わったり。例えばレイアウトにバグがあった場合、JavaServletをみたり、JSPをみたり、HTMLやCSSみたり、JavaScriptの実行にエラーがないか確認したり、AJAXで得られたレスポンスの内容をみたりと、かなり開発環境としてカオスであり、不自然な訳です。
あと、JavaScriptのコード量が多くてチーム開発とかだと、コーディングルールをがっちり決めて、ソースコードレビューをしないとめちゃくちゃなコードを書く人が現れたりします。クラス定義の書き方がみんなバラバラだったり、コンストラクタ以外でメンバ変数が勝手に追加されていたり。
//とりあえずここに置いておく
みたいなコメントと共にソースコードの中ほどにメンバ変数が増やされていたりすると、もう死んでくれ!と叫びたくなる訳です。
そもそもJavaScriptのような静的な型がない言語は、IDEの補間が効かなかったり、参照検索がなかったり、実行時にならないと変数名やメソッド名を入力し間違えたことが分からなかったり、名前変更のリファクタリングが効かなかったりと、効率が悪いので好きではありません。
次に作るWebアプリは、JavaScriptに代わる言語で開発したいなと思い、ずっと、代わるものを検討、ウォッチしていました。Dart, Haxe, TypeScript の3つをウォッチしていたのだけど、Haxeは調査済みで一部のプロジェクトで使用し始めています(気が向いたらHaxeのエントリも書きます)。
今回は、GoogleDartを使ってみたのでそのレポート。

Dart言語の特徴

昨年秋に、安定板のDart Editorが出ました。
Dart Editor + Dart SDKは、 http://www.dartlang.org/ ここからダウンロードできます。Dartの特徴は、

  • デスクトップWeb、モバイルWeb、サーバ用と多用途
  • DartVMでも動かせるし、JavaScriptコンパイルしても動かせる
  • 簡素で、効率よく、スケーラブル
  • 分かりやすくて、読みやすい文法
  • Dart言語は、ライブラリもパッケージマネージャもエディタもオープンソース
  • 開発効率を上げるPubパッケージマネージャ
  • DartVMは高速。JavaScriptエンジンのV8の倍速

という感じです。DartVMネイティブで動かせるのは、当面サーバサイドで使うときでしょう。普通は、dart2jsでJavaScriptに変換してWebブラウザの中で使うのが普通でしょう。

主な言語仕様

言語仕様の特徴としては、

  • クラス:prototype baseは色々問題あるよねというところですね
  • クラスの外のトップレベル関数Javaと違ってクラスの外にも関数書けるよと
  • オプション引数:省略可能引数、C++のように引数リストの後ろの方を省略する方法と、名前付き引数にしてどこを省略してもよい2通りがある
  • 文字列インターポレーションPerlのような"Hello, ${name}"みたいな置換ですね
  • マルチライン文字列Perlのヒアドキュメント($s=<<END;)みたいな文字列リテラルですね
  • 動的メソッドハンドリング:実行時にメソッドがあるか確認する(静的にチェックしてほしいのだが)
  • カスケード演算子(..)VBJavaScriptのwithみたいなものですね
  • オプショナルな型:小さなアプリをさっと書くときは型を書かない、ちゃんとしたアプリは、型を書く(私は厳密な型が欲しい)
  • レキシカルスコープ:ちゃんと{}のブロックが識別子のスコープですよ。クロージャも対応しているよ。ローカル変数キャプチャもしているよと。ま、JavaScriptだけが異常だとは思うが。
  • ライブラリ:ライブラリを使うときも作るときも切り替えとかの修正不要。Pubというオープンなリポジトリがかなりお手軽で便利
  • Isolate(アイソレート):マルチスレッドでメモリを共有して管理するのは、開発もデバッグも大変。Isolate同士は、お互いのメモリにアクセスできないので安全

という感じ。ま、モダンな言語仕様ですね。使ってみた感想としては、やはりC++Javaが好きな僕としてはメソッドがあるかどうかを動的に判断するのが辛いか。一応、Dart Editorで黄色い下線がでて警告がでるときもあるけど。
あとは、メソッドのオーバーロードができない。JavaScript風にしたのか、オプショナルな型のせいか、オーバーロードがあるとソースが読みにくいという意図なのか、JavaScriptコンパイルするときに美しくないからか。
あとは、intが初期値を代入していないときは、nullになったり。あたりがちょっと気になったところ。

classの例

// 形状インターフェース
abstract class Shape {// abstractが使える
  num perimeter();// num型は、int型とdouble型のsuperクラス
}

// 矩形クラス
class Rectangle implements Shape {//implementsでShapeインターフェースを実装
  final num height, width; //finalは初期値代入のみ可能で以降変更不可
  Rectangle(this.height, this.width);    // コンパクトコンストラクタ表記。引数でメンバー変数を代入するという意味。
  num perimeter() => 2*height + 2*width; // 関数の省略表記。関数本体が単文のときは、このように省略できる。
}

// 正方形クラス
class Square extends Rectangle {// extendsで矩形を継承(継承は1つまで)
  Square(num size) : super(size, size);//コンストラクタで親クラスのコンストラクタを呼ぶときはC++風にコロンを使う、superという予約語を使うところはJava風。
}

ま、これを見るだけでもモダンな感じですね。

Dart Editorの特徴

IDEであるDart Editorの特徴は、

  • コード補完:メンバ変数やメソッド名が補完される
  • リファクタリング機能:リネームとか
  • アウトラインビュー:クラスのメンバ変数やメソッドがツリー表示
  • デバッガー:ブレイクポイント、変数を見る、ステップ実行
  • 静的解析:メンバ変数じゃないとか、代入してないとか静的解析で警告がでる
  • 呼び出し元検索:呼び出し元検索ですぐにそこを表示できる

という感じ。
使ってみた感想としては、コード補完が効かなくなることがあってDart Editorを再起動したら直ったとか、リファクタリングのリネームは、ライブラリを超えては無理なのか、効かないときがあった。とか、呼び出し元検索は1度も動かなかった。など普段書いているJavaの安心感に比べると心もとない。

作ってみたアプリ


3時間くらいで作ってみたアプリが上記。htmlファイルをほぼ書かず、cssファイルをほぼ書かず、コードでウインドウを作ったり、コントロールを配置したりするWindow Toolkitっぽいもの。クライアント側は、html,cssのコードは書かずにdartだけ書いて、サーバサイドもcssやhtmlはほとんど出力せずにjsonとかだけを出力するシステムをイメージしたもの。
なお、Pubというオープンなリポジトリがありpubコマンドというライブラリマネージャがあり、他の人が作ったライブラリをインストールしたり、アップデートしたり、自分の作ったライブラリをアップロードするのはとても簡単らしいが、pub installコマンドで、自分が書いていたライブラリが削除されるというミスをしてしまいました(そしてオレオレWindow Toolkit作り直し)。pubに関してはまだ調査不足なので、みなさん自分のライブラリを作っている場合は、pubコマンドの使用前にはバックアップしましょう。

#dartをしばらく調査したら、次はTypeScriptを調査予定〜

――
アプリ受託開発、Webシステム受託開発も行っています。以下自社製品。
URL:ibisMail Freeのダウンロード、レビューはこちら
URL:ibisMail for iPhoneのダウンロード、レビューはこちら
URL:ibisMail for iPadのダウンロード、レビューはこちら
URL:ibisPaintのダウンロード、レビューはこちら サイトは、ibispaint.com
ご意見ご要望はTwitterから: @kamiyan

AWSのEC2の自動リブート

AWSに引っ越したら、物理サーバでは発生しなかったフリーズ(CPU100%で張り付く)が、発生しました。
そこで、監視プログラムを緊急作成しました。


上記プログラムのダウンロードは、 http://bit.ly/104LVnC こちらから。

AWS引っ越し、1か月目の課金が$5000オーバー。ここから削減して1/3ぐらいに減らしたい。ここからが腕の見せ所(^_^)/

――
アプリ受託開発、Webシステム受託開発も行っています。以下自社製品。
URL:ibisMail Freeのダウンロード、レビューはこちら
URL:ibisMail for iPhoneのダウンロード、レビューはこちら
URL:ibisMail for iPadのダウンロード、レビューはこちら
URL:ibisPaintのダウンロード、レビューはこちら サイトは、ibispaint.com
ご意見ご要望はTwitterから: @kamiyan

ZABBIXのススメ

先週は、AWSへ引っ越しについてエントリを書いた。今週は、サービス監視について。
引っ越し前は、MRTGを使っていた。サービス監視というよりは負荷の可視化ですね。SNMP(Simple Network Management Protocol)を使って得られるメトリクスをグラフ化していました。CPU負荷、メモリ使用量、ディスク使用量、ネットワークトラフィック(通信量)、プロセス数などをdaily, weekly, monthlyでグラフ化します。グラフ化されていると、問題が起こったときに何が原因か、または、問題が起こる前に予測ができます。引っ越し前は、それとは別にサービス監視(死活監視)としてPerlスクリプトを使ってプロセスが生きているかを監視して死んでいたら再起動させるなどをしていました。
MRTG導入時は、7年ぐらい前だったので、そろそろ古めかしいシステムでなく近代的なものにしたいなと思って探したところZABBIXがよさげだったので導入しました。

上図が、ダッシュボードのトップ画面です。ホストをグループ分けして、どのグループに警告がでているか、エラー(エラーのときは赤くなる)が出ているかわかります。グループをクリックすればさらにホスト一覧へとドリルダウンできます。警告とするトリガーやエラーとするトリガーも設定できます。グループ管理できることで、数百台の場合でも管理できるようになっているのでしょう。また、大量のメトリクスやトリガーやイベントがテンプレートとしてデフォルトでインストールされるので、ホストを追加したらテンプレートを追加するだけで、大量のメトリクスなどが一気に追加されて便利です。

上図が、CPU使用率です。Load Averageと違って、User Time、System Time、I/O Wait、Nice、Interrupt、Soft IRQ、Steal Timeなども色分けして表示してくれます。また、負荷が高そうな部分をズームしたい場合でもその辺りを横にドラッグして選択すると選択範囲が拡大されます。

MRTGよりよくなったこと

  1. MRTGではサーバ上の設定ファイルを作成、設定値の追加、編集が必要であり、ホストの追加・削除の度にサーバでの作業が必要であったが、ZABBIXではWebUIでホストの追加、メトリクスの追加・削除・編集が可能になった
  2. MRTGではメトリクスの可視化(グラフ化)のみであったが、ZABBIXはトリガとかイベントで通知を送る機能などがある
  3. MRTGではグラフのグループ化は、HTMLを書くなどの必要があったが、ZABBIXではWebUIでグループ化ができたり、グラフ一覧表ページ(Screenと呼ぶ)を追加・編集・削除ができる
  4. MRTGではdaily, weekly, monthlyと固定的であったが、ZABBIXではデータはRDBに保存されており(アイビスではMySQLを使用)、グラフのビューも表示スパンがWebUIで自由に変更できる

ZABBIXの使いにくいところ

いいことづくめにみえますが、使いにくいところもあります。

  1. Screenの編集が使いづらい。全体的にプルダウンを選択したら全画面リロードになって使いにくい。特にScreenの編集がつらい。色々なところをAJAXにして部分書き換えになってくれるとよい。

というところです。WebUIが重いと結局テキストファイルで編集した方が早いのか?とも思えてきます(が、テキストファイルはテキストファイルでミスしやすいのが問題なのだが)。一応、XMLでExportして、テキストエディタで編集してImportもできるのですが、XMLの編集自体が面倒。強力なXMLエディタを探した方がよいのかもしれません。

今のところプラグインを作成して独自のメトリクスを追加というのはやっていません。この辺は研究してコツコツ追加してゆきたいと思います。

#今日は、選挙でしたね!早く帰って選挙結果のTVをみたい。

――
アプリ受託開発、Webシステム受託開発も行っています。以下自社製品。
URL:ibisMail Freeのダウンロード、レビューはこちら
URL:ibisMail for iPhoneのダウンロード、レビューはこちら
URL:ibisMail for iPadのダウンロード、レビューはこちら
URL:ibisPaintのダウンロード、レビューはこちら サイトは、ibispaint.com
ご意見ご要望はTwitterから: @kamiyan

AWSでの3種類のネットワーク構成

先日、アイビスのサーバの引っ越しがありました。引っ越しは毎度毎度大変です。データセンターに置いていたサーバ群ですが、大変なのでやりたくないですが、引っ越しをせざる負えない事情がありまして、データセンターからAWSサーバ40台の引っ越しをしました。
AWSは、受託開発では経験があるものの自社サービスでは初めて。僕も大急ぎで勉強して、晴れてAWSデビューです。

システム開発は、企画、要件定義、設計、実装、試験、運用とありますが、どれも楽しいですね。ものづくり万歳!。今回は、ネットワークアーキテクチャのお話です。ネットワークアーキテクチャを考えるとき、拡張性はあるか?可用性はあるか?負荷に耐えられるか?運用しやすいか?コストを下げるにはどうするか?と色々考える必要があって楽しいですね。

AWSの話の前に、まずは一般的な三層モデルの解説から。

クライアント(Webブラウザまたはアプリ)からApacheへ接続して、その後ろにAP(アプリケーションサーバ)があり、APからDBに接続という形。うちの自社製品は基本JavaなのでWebアプリケーションはTomcat上で動いています。
Apache-Tomcat連携しているのは、本来は静的コンテンツはApacheが返し、動的コンテンツはAPが返すためであり、一般に静的コンテンツはTomcatが返すよりApacheが返す方が速いためです。
また、Apacheのバーチャルホスト機能を使うためにも先頭にApacheがいます。バーチャルホスト機能は、複数のWebサービスを1台のホストで処理するために必要で負荷が高くないサービスが複数ある場合は、Apache1台に複数サービスを集約してコストを削減します。例えばDNSサーバの設定で、www.example1.jpとwww.example2.jpの2つのドメインを同じIPに設定し、そのホストにApacheを立てて、ブラウザからのリクエストのドメインを見て、振り分け先のAPを切り替えるという形で使います。
3つ目の理由として、mod_rewriteがあります。mod_rewriteは機能が強力過ぎて悪魔のようなモジュールと一部で言われていますが、URLを正規表現でマッチさせて、マッチしたらAPへフォワードするときにURLを書き換えて転送させることができます。例えば、

http://d.hatena.ne.jp/kamiyan2/20121006

というリクエストをフロントのApacheが受け付けたときに、正規表現で「kamiyan2」と「20121006」をマッチさせて、

http://d.hatena.ne.jp/entry.jsp?uid=kamiyan2&eid=20121006

のようにURLを書き換えて、APに転送させることができます。
RESTfulが流行っていますが、それ以前に検索に引っかかりやすくするために同じリソースを指すならPATHに含めましょうというところですね。
4つ目の理由として、図に「LB」と書いてあるようにロードバランサとしてのApacheですね。mod_proxy_ajpとかmod_proxy_http、mod_jkなどを使ってAPを増やして分散処理をします。

その1:最小サービス(AP + DB)


AWSへの引っ越しで最も負荷の小さいサービスは、EC2を1台、RDSを2台(他のサービスと共有)としました。しかもEC2は、マイクロインスタンス(月額約$19)。DBは、RDSのミディアムインスタンス(月額約$172)。RDSミディアムインスタンスは負荷の割にオーバースペックになっていますが、これは他のサービスとの共有で使うために大き目になっています。もう1台RDSは、リードレプリカです。このリードレプリカには、APからのアクセスはしておらず、単なるバックアップ目的です。ファイルにバックアップを取って運用だと障害時に復旧に時間がかかるのでレプリケーションしています。年々サーバ側のデータは増えていく時代なので、バックアップからの復旧前提のシステムは、やるべきではないでしょう。
ちなみに上図のアイコンはAmazonが配布しているアイコンで、ネットワーク図を書くには欠かせないアイコンです。こういう標準化をしているところは、Amazonのよいところですね。
なおEC2インスタンスは、Terminateするとインスタンスストレージに保存した情報は消えます。ストレージの情報が消えないようにするにはインスタンスストレージでなくEBS(有料)を使うとお金は余分にかかるけど、ちょっと安心です。
うちのシステム群では、LBやAPで使っているのは基本はキャッシュで、重要な情報は保存していないのでEBSは使いませんでした。
また、別途AMI(イメージファイル)から起動するとプライベートIPもグローバルIPも動的に決められてしまうので、注意が必要です。なおTerminateではなく、stop/startやrestartではIPは変わらないので大丈夫です。Terminateは、停止というよりは、破棄ですね。OSが起動しなくなった、reboot依頼が効かない、SSHで入れなくなったなどのときに使うようですが、今のところそのような事態になったことはないです。
IPに関しては、EIPでグローバルIPを固定IP化しました(ほぼ無料)。ちなみにEIPは初期設定では5件までしか作成できません。これ以上欲しい場合は、Amazonへ連絡する必要があります。グローバルIPが貴重だからでしょうかね(うちは今のところ5件以内で済んでいます)。

その2:スタンダードサービス(CDN+LB+AP+DB)


標準的なネットワーク構成となったのは、 http://ibispaint.com 。ibisPaintは、静止画を大量にレスポンスするため事前にCloud Flare(無料CDNサービス)を追加していました。Cloud Flareは、fladdictさんのエントリーで紹介されていたので早速入れました。
AWSでは、WAN接続のデータ通信でも課金されるため、データ量を抑える必要がありますが、静的コンテンツをCDNサーバがレスポンスしてくれるため、AWS側のサーバ群までリクエストが来る量が減りコスト削減になります。ibispaintの実績ではトラフィックの60%を削減できました。通信量のみならずDB負荷も下がりました。また、CDNサービスは世界中にコンテンツキャッシュサーバがあるので、クライアントの近くのサーバが応答するようになっていて、応答が早くなる効果もあります。ibisPaintは、USやオーストラリア、アラブ、タイ、韓国、台湾などからもアクセスがあるので少しはレスポンスタイム削減にも役立っているかもしれません。なおCloud Flareを使うと、クライアントから最寄りのコンテンツキャッシュサーバにアクセスを振るために、DNSサーバはCloud Flareのものになります。また、動的コンテンツも含めいったんCloud Flareのサーバを経由してリクエストが自分のサーバに届きます。CDNサービスはAWSでもあるのですが、有料であるためCloud Flareを利用しました。無料でこれだけのサービスを受けられるとは驚愕です。
CDNの次に、ELB(ロードバランサ、月額約$18)を置きました。データセンターのころは、Apache mod_proxy_ajpによるバランシングでしたが、せっかくAWSに引っ越したということでELBを使いました。ELBにはSSHでログイン等はできずAWSのWebUIなどから設定します。ELBの管理はほとんどお任せ状態で、ELBに障害が発生した場合も、勝手に他のホストで継続するようで、固定IPではありません。そのため、CDNからELBのホストへの参照はドメイン名での参照(DNSの設定でCNAME)になります。今のところELBでの障害は経験してませんが。
ELBの後ろにAPサーバを設置。このEC2は、Apache-Tomcatの両方を同じホストにいれました。データセンターのころは、Apacheは主にロードバランサとして使っていたので、ApacheTomcatを同居させることはなかったのですが、AWSでは同居させることにしました。ApacheからTomcatへの接続では、Apache側の設定でTomcatを指定する必要がありますが、別のEC2インスタンスの場合、EC2インスタンスを新規に立てる度に、プライベートIPが決まり、その度にApache側の設定ファイルを編集しなければならないのは、手間が大きいと判断し、Apache-Tomcatを同居させて、Apache側設定ファイルでは、Tomcatのホストをlocalhostとすることで、APの追加作業が楽になるようにしました。本当は、Apache自体をなしにして、ELBからTomcatへ直でつなぎたかったのですが、mod_rewriteを使っており、ELBにはmod_rewrite相当の機能がなかったので、Apache-Tomcatとなりました。この辺り、将来新しいサービスを設計するときは、mod_rewrite不要なフレームワークを使っていきたいなと思います。上図では、4層モデルにみえますが、Apache-Tomcatと2プロセスがあるので、5層モデルになってしまっています。
EC2のインスタンスは、スモールインスタンス(月額約$66)を2台としました。ibisPaintはDB負荷やトラフィックは大きいもののAPの仕事はほとんどないので、APのCPU負荷は低いですが、可用性(サービス停止回避)のために2台にしています。
RDSの負荷は高いですが、今のところAPからDBへの接続はマスターのみで、レプリカは相変わらずバックアップ用です。間もなく参照系クエリをレプリカへも振ることになるでしょう。RDSは、ラージインスタンス(1台あたり月額約$345)を使っています。
ちなみにibisPaintの投稿された作品(静止画など)は、すべてDBに保存しているため、バックアップに数時間、リストアに数日かかるため、もはやバックアップとリストアという選択肢はほぼないに等しいです。
また、データセンターからAWSへの引っ越しでは、バックアップしてリストアでの引っ越しでは、サービス停止時間が長すぎるため、必要最低限のテーブルだけバックアップとリストアをして(その間だけ短時間サービス停止)、メインの大量のデータは、徐々にデータセンターからAWSへバッチで移動させて、バッチでのデータ移動中は、AWSのDBにデータがなければデータセンターにリクエストを振る(フォワーディング)ようにAPのプログラムを書き換えて、引っ越しをしました。
大きなデータが数日かけて、ゆっくりと移動してゆく様は、なんともかっこいいですね。

その3:DNSラウンドロビン+自社製LB+AP+DB


今回紹介する最後のネットワーク構成です。
こちらのサービスでは、ELBは使わずに自社製のロードバランサを使いました。理由は、ELBではステッキーセッション(同じユーザからのリクエストは同じAPに振る機能)のセッションキーとしてCookieしか使えず、このサービスはガラケーからのアクセスもあるためセッションキーはURLに埋め込んであるためです。i-modeブラウザが長らくCookie非対応だったためやむなくURLに埋め込んであります。なお、Apacheのmod_proxyなどのロードバランサ機能を使わず自社製となっているのは、単にこのシステムを立ち上げるときに、mod_proxyがまだなく、mod_jkかmod_jk2しかなく、また、それらが不安定であったために開発しました。おかげでWebUIでの負荷係数の変更やAP追加や切り離しができたりと使い勝手は良いです。
さらにこのシステムでは、リクエスト頻度が高いためLBを2台をEC2(スモールインスタンス、月額約$66)+EIP(固定IP)で建てて、DNSラウンドロビンで2台に振り分けています。
その後ろのAPは上図では3台ですが実際には16台となっています。APは一部ハイCPUエクストララージ(月額約$547)です。EC2インスタンスは初期設定ではアカウントにつき20台までしか追加できません。それ以上はAmazonへ問い合わせをする必要があります。恐らく後払いシステムなので与信不足とならないようにではないかと思います。
LBからAPの参照は現状hostsファイルにAPを書くことで対応していますが、この辺りはスクリプト等を書いて、自動化したいところです。

RDSの注意事項

RDS(MySQL)は、SSHでログインできません。mysqlコマンド等でアクセスすることはできます。また、my.cnfの設定ファイルの項目と同様の設定が、AWSのWebUIから設定できます。しかし、my.cnf設定項目の一部は設定不可になっているので注意です。特にtimezoneの指定ができず、APから接続開始時にセッション内変数で変更してアクセスするか、AP側のプログラムを修正する必要があります。
あとSSHでログインできないのでスローログとかも出せません。正確には、ログファイルとして出力させることができないので、テーブルに出力させることになります(現状、まだ試していない)。
2013/03 追記 AWSのアップデートでログの閲覧のWebUIとAPIが追加されました。

価格はどうなの?

現状、コストの内訳は、

といった感じで圧倒的にEC2でコストがかかっています。割とAPのCPU負荷が高いシステムというのもあると思いますが、現状は余分目にAPを立てているので高いというのがあります。これでデータセンターと同額程度のため、ここからチューニングをして台数を減らせば、半分ぐらいになるのではないかなとみています。また、現状の契約はオンデマンドインスタンスですが、リザーブインスタンスの契約に変えればさらに1/3ですむので、データセンターよりかなり安くなると思います。サーバを増やすのも減らすのも実サーバより手間が少ないのでかなり気楽に調整できるのがいいですね。

さいごに

サーバ40台の引っ越し、サブシステムも含めると20くらいあるシステム。大変な難題プロジェクトなので、緊張の連続でした。とてもハードルが高い作業でしたが、チームメンバー全員猛烈な勢いで勉強し(全員AWS未経験)、上級のネットワークエンジニアと、これがいいんじゃないか、ダメか?とネットワークアーキテクチャについて議論をしつつ、調査、修正、作業手順書作成、実行、試験を短時間でやりとげ、心すり減らしつつも大変有意義でした。
これから自動化スクリプトや、事故が起きたときを想定したマニュアル作りと、復旧訓練、サービス監視のチューニングや、コスト削減のためのチューニングがありますが、またノウハウをコツコツと貯めていきたいと思います。
クラウドに移ってやはりスケールアウトもスケールアップも楽になったのは気が楽ですね。
引っ越しプロジェクト、とても貴重な経験です。
需要があればサービス監視についてのエントリも書こうと思います。

――
アプリ受託開発、Webシステム受託開発も行っています。以下自社製品。
URL:ibisMail Freeのダウンロード、レビューはこちら
URL:ibisMail for iPhoneのダウンロード、レビューはこちら
URL:ibisMail for iPadのダウンロード、レビューはこちら
URL:ibisPaintのダウンロード、レビューはこちら サイトは、ibispaint.com
ご意見ご要望はTwitterから: @kamiyan

技術者向け iOS6 新機能!


上図、左がiPhone5、右がXperia GX

iOS6でましたね〜。iOS6の発表は、夜中の2時にネットの前に待ち構えて、ワクテカしながら発表を聞いていました。毎度毎度テンションが上がりますね!
iPhone5でましたね〜。僕は、ソフトバンクのオンラインショップで発売日の16時に予約サイトに行って、サーバ混雑中と1時間ほど格闘しながら予約しました。発売日の翌日にiPhone5が届きました〜。
iOSディベロッパーならみんなMAX盛り上がるときだよね。祭りだ!祭りだ!
さて新しいOSがでたら必ず新機能をチェックすべし!アプリ企画者、ディレクター、プログラマならみんなチェックすべし!どこに企画のネタが落ちているか分からないし、どこに自分のアプリの改善可能点があるか分からないし。これは必須!という訳で毎度おなじみのiOS Dev Centerの新機能紹介ページの翻訳です。いつもOSリリースと同時にブログにアップしていたのだけど、ちょっとバタバタしてで遅れました。だってiPhone5が縦長なんてギリギリまで発表されないんだもの。みんなアプリのアップデートで死にそうでした。
という訳でいつもどおり適当意訳もあるけど行ってみよう〜。

iOS6新機能

マップ

アップルが提供する新しい地図に加えて、「マップ」アプリとMapKitフレームワークは他のアプリとのやり取りを新たにサポートします。現在協調動作をしない独自地図アプリが、「マップ」アプリを起動したり、興味のある場所やルート情報を表示したりする簡単な方法が用意されています。ターンbyターンナビゲーションサービスのようなルート情報を提供するアプリは、ルーティングアプリとして登録でき、システム全体で有効なサービスを作ることができます。
ルーティングアプリとして登録することはユーザの目の前にサードパーティのアプリを取得する機会をより多くします。ルーティングアプリは単にドライブやウォーキングに限定されるものではありません。ルーティングアプリはユーザの好きな自転車やハイキング、航空路、地下鉄、その他の公共交通機関のルートを提供します。そしてアプリはユーザのデバイスにダウンロードする必要がありません。「マップ」アプリは、AppStoreでルーティングアプリについて知っていて、ルート情報のためにそれらのアプリを購入するオプションをユーザに提供することができます。
ルート情報を提供していないアプリでも「マップ」アプリとルーティングアプリの両方のメリットを得ることができます。アプリは特定の位置を表示することや2地点のルート情報を表示する新しいインターフェースを使うことができます。
アプリでルート情報を提供する方法や、「マップ」アプリでルート情報を表示する方法の詳細は、Location Awareness Programing Guide参照してください。


かみやん:何かと残念な地図で世間をお騒がせしているApple製地図ですが、なんかルーティングアプリとか新しい仕組みがあるみたい。それに対応するとユーザがアプリをダウンロードしなくても、そのアプリを提示してくれてダウンロード機会を増やしてくれるって書いてあるけど、どんな感じなんだろう。ぐるなびみたいなアプリはチャンスなのかな?

ソーシャルフレームワーク

Social Framework(Social.framework)は、ユーザのソーシャルメディアアカウントにアクセスするシンプルなインターフェースを提供します。このフレームワークは、iOS5で導入されたTwitterフレームワークにとって代わり、Facebookや中国のWeiboサービスが追加されました。アプリはこのフレームワークを使うことによってユーザのアカウントでステータス更新や写真の投稿ができます。このフレームワークはAccountsフレームワークと連携して、ユーザにシングルサインオンを提供し、また、ユーザの承認されたアカウントにアクセスすることを保証します。
UIKitフレームワークは、ユーザがいくつかの選択された内容に基づいて実行できるアクションを表示するための新しいUIActivityViewControllerクラスを提供します。このクラスの機能は、ユーザがTwitterFacebookなどのソーシャルアカウントにコンテンツを投稿できるようにすることです。アプリは、iPhone上でモーダルにこのクラスを表示したり、iPad上でPopoverのコントローラの利用で表示したりすることができます。ユーザがいずれかのボタンをタップすると、View Controllerは、関連付けられたアクションを実行するための新しいインターフェースを提供します。
ソーシャルフレームワークについて、より詳細な情報は、Social Framework Referenceを参照してください。UIActivityViewControllerクラスについては、UIKit Framework Referenceを参照してください。


かみやんFacebookに投稿も楽になった。これはいい!あとSafariの共有ボタンのようなUI、UIActivityViewControllerで簡単に実装できるんですね。共有が簡単に実装できる。これもいい!

Pass Kit

Pass Kitは、Webサービス、新しいファイルフォーマット、Objective-C(PassKit.framework)を使い、ダウンロード可能なパスを実装した新しい技術です。企業は、クーポンや搭乗券や企業向けの割引券などを表すパスを作ることができます。これらの物を表す物理的な物を持ち運ぶ代わりに、ユーザはiOSバイスにこれらを保存し、今までと同じような使い方で使うことができます。
パスは、サードパーティWebサービスで作成され、メールやSafariやアプリで配信されます。パスそれ自身は、特別なファイルフォーマットで、配信前に証明され暗号化されています。ファイルフォーマットは、ユーザが何のためのサービスなのかを知るための関連ある識別情報を持っています。またバーコードや、景品交換をしたり使ったりできるようにカードを検証するその他の情報が含まれている場合があります。
PassKitや、アプリでPassKitをサポートする場合のより詳細な情報は、Passbook Programing Guideを参照してください。


かみやん:財布の中のカード類が全部なくなる時代がくるといいね!道のりは長いと思うけど、流行って欲しい!

Game Center

Game Centerは、ゲームを遊ぶ体験を進化させるために追加された新しい機能があります。GameKitフレームワークに追加された主要な変更は以下です。

  • チャレンジは、アチーブメントやスコアを友達と競争することができます。プレイヤーはGame Centerアプリからチャレンジを発行することができます。アプリは、GKChallengeクラスを使ってゲームにチャレンジの発行をさせることができます。
  • GKGameCenterViewControllerクラスは、以前のリーダーボード、アチーブメント、友達リクエストのビューコントローラの機能をすべて統合しています。アプリは以前のビューコントローラを使うことができますが、それらは新しいGame Center ビューコントローラの中のタブとして表示されます。
  • ローカルプレイヤーの認証のプロセスが変更になりました。新しいプロセスでは、アプリはすべての認証ステップを実行するブロックをauthenticateHanderプロパティに設定します。一度、設定したらGameKitは必要なときにローカルプレイヤーを自動的に認証します。しかしGameKitは直接、認証インターフェースを表示しません。代わりに、ハンドラは提供されている認証ビューコントローラを表示し、認証UIが表示されたときにより正確に制御できる機会をアプリに与えます。
  • (訳注:その他、ターン制ゲームとか、GKMathmakerクラスとかいくつかあるけど興味がないのでパス)


かみやん:友達とチャレンジ!というのがついたみたいだね。まだ試してないや。

リマインダ

Event Kitフレームワークユーザのデバイスでリマインダを作成したりアクセスしたりするインターフェースが追加されました。アプリが作成したリマインダは、ユーザが作成したものと一緒にリマインダアプリに表示されます。リマインダは、位置ベースや時間ベースのアラームも含むことができます。
リマインダの生成の新しいインターフェースを含むEventKitフレームワークについて、より詳細な情報は、Event Kit Framework Referenceを参照して下さい。


かみやん:ついにリマインダもAPIが公開された。リマインダ置き換えアプリとかも出そうですね!

アプリ内課金

StoreKitフレームワーク(StoreKit.framework)は、新たにアプリ内でiTunesのコンテンツを購入する機能を提供し、Appleのサーバのダウンロード可能なコンテンツを取得する機能を提供します。アプリはビューコントローラを表示し、ユーザにiTunesのアプリや音楽や本や他のコンテンツをアプリ内から直接購入させることができます。アプリは購入のために利用できるようにしたいコンテンツを識別しますが、購入処理の残りの部分はStoreKitが処理します。
iOS6より前は、開発者はアプリ内課金を通して利用可能にするダウンロード可能な任意のコンテンツを管理するために対応する必要がありました。ホスティッドダウンロードは、新たにユーザに利用可能にするために開発者がしなければならないことを簡素化します。新しいSKDownloadクラスは、コンテンツのダウンロード可能な一部を表します。加えてSKPaymentTransactionクラスは任意のホスティッドコンテンツのためにダウンロードオブジェクトの配列を提供するように変更されました。コンテンツの一部をダウンロードするために、アプリはダウンロードオブジェクトをペイメントキューに追加します。ダウンロードが完了したら、ペイメントキューオブザーバーに通知されます。
Store Kitフレームワークのクラスについての詳細は、Store Kit Framework Referenceを参照してください。


かみやん:なんと!アプリからiTunesのアプリや音楽や本が買える!アフィリエイトフィーとか入るのかね。うまく連動したコンテンツ紹介アプリとか増えそうですね。

コレクションビュー

UICollectionViewクラスはユーザに順序付けされたデータを表示する新しい方法を提供します。コレクションビューオブジェクトを使用すると、アプリは埋め込まれたビューの表示と配置を定義することができます。コレクションビュークラスは、独立したデータ項目の配置を定義するために付随したレイアウトオブジェクトと密に連携して動作します。UIKitは、標準またはさまざまなサイズの項目を含む複数列のグリッドを実装するために利用できる標準的なフローベースのレイアウトオブジェクトを提供します。またグリッドレイアウトより良いものを作りたい場合は、開発者が選択した任意のレイアウトスタイルを提供するために、カスタムレイアウトを作成することができます。
コレクションビューは項目の表示をサポートするための専用のクラスのグループで動作します。セルに加えてコレクションビューは補助的なビューと装飾ビューを持つことができます。これらのビューの使い方と配置は、レイアウトオブジェクトによって完全に制御されます。例えば、フローベースレイアウトオブジェクトは、コレクションビューの中のセクションのためにヘッダとフッタのビューを実装するための補助的なビューを使用します。
その他のコレクションビューの注目すべき特徴としては次のようなものがあります。

  • アプリのコレクションビューの表示を管理するためにUICollectionViewControllerクラスがあります
  • コレクションビュー内のコンテンツのアニメーションをサポートします
  • 項目の挿入、移動、削除を処理します
  • セルや他のビューを生成したり管理したりするために簡素化されたモデルを提供します。

アプリのUIとしてコレクションビューを使うことについての詳細は、Collection View Programing Guide for iOSを参照してください。


かみやん:@fladdictさんがUITableViewControllerをハックしてカスタマイズをiphone_dev勉強会で発表していたけど、汎用化されたということかな?

UIステート保存

ステート保存は、アプリに対してユーザが最後に使用したときのユーザインターフェースの状態を復元することをより簡単にできるようにします。iOS6より前では、アプリの終了イベントで現在のUIの状態についての情報をアプリが保存することが推奨されていました。再起動するとアプリは、その情報を使いUIの状態を復元し、アプリが終了してないように見せていました。UIステートでは、アプリのユーザインターフェースを保存したり復元したりするために基盤を提供し、このプロセスを簡素化します。
ステート保存の実装では、アプリのユーザインターフェースのうちどの部分を保存するのかを識別する労力はまだ必要となります。加えて、保存と復元のプロセスは、再起動の間にコンテンツが欠落したりアプリのUIが変化したりといった、不測の事態に備えてカスタマイズすることができます。
ステート保存についてより詳細な情報は、iOS App Programing Guideを参照してください。


かみやん:ibisMailでは、これ自力でやってますね。システムが面倒見てくれると楽になりますね。

オートレイアウト

オートレイアウトは、以前にUIの要素をレイアウトするために使用された「スプリング・ストラット」モデルを改良したものです。オートレイアウトでは、アプリのUIの要素をレイアウトする方法についての規則を定義します。これらのルールは、スプリング・ストラットモデルよりも、より関連の大きなクラスを表現し、より直観的になります。
オートレイアウトで使用されるエンティティは、制約と呼ばれるObjective-Cのオブジェクトです。この方法は、多くのメリットをもたらします。

  • ローカライズで文字列を交換したときに、レイアウトを修正しなくてよい
  • ヘブライ語やアラブ語のような右から左の言語のときにUI要素を反転しなくてよい
  • ビュー内のオブジェクトとコントローラーレイヤーの間での調整がよりよくなる

大抵、ビューオブジェクトは親ビューと関連する兄弟ビューの中で標準的なサイズと位置についてのベストを知っています。コントローラは、なにかしらの理由で非標準を要求されたときは、それらの値を上書きすることができます。
オートレイアウトについてより詳細な情報は、Cocoa Auto Layout Guideを参照ください。


かみやん:オートレイアウトの発表のときはMac向きな機能だなと思ったら縦長iPhone5のためだったか?拘束ベースで、ここのビューだけ高さを拘束しないけど、他のビューは上に拘束とか下に拘束みたいにするのかな。

データプライバシー

位置情報に加えて、システムは新たに次のユーザのデータにサードパーティアプリがアクセスする前にユーザに許可してよいかを問い合わせます。

  • 連絡先
  • カレンダー
  • リマインダ
  • フォトギャラリー

連絡先、カレンダー、リマインダデータでは、アプリはそれらの項目にアクセスが拒否された場合に備えて準備をしたり、拒否に応じて動作を調整したりする必要があります。もしアクセス許可のプロンプトを表示してない場合は、得られるデータ構造は有効ですが、レコードはありません。もしユーザがアクセス拒否をしたらアプリはNULL値か空のデータを得ます。もしユーザがアプリに許可を与えたら、システムはデータをリロードしたり復元したりする必要があることをアプリに通知します。
フォトライブラリでは、既存のUIで、アクセス拒否に対応しています。
アプリはInfo.plistファイルでどのようにデータを使用するつもりであるかの説明を提供をすることができます。アクセスのためにシステムがプロンプトを表示する必要があるときにユーザにその説明が表示されます。
Info.plistファイルにどのキーを足す必要があるのかについての詳細は Information Property List Key Referenceを参照してください。個別のデータへのアクセスについては、関連するフレームワークリファレンスを参照ください。


かみやんガラケーiアプリのときはプライバシーガチガチで自由度がなかったけど、初めてiPhoneAPIをみたときは緩すぎて怖かったけど、やはりつきましたね。が、しかし、アプリディベロッパからしたらサポートが増えるだけ。。ibisMailもibisPaintも連絡帳の名前が表示されませんとか、画像がフォトライブラリに保存できませんとか、よく来ます。。

フレームワークの機能拡張

前の節で説明した項目に加えて、以下のフレームワークは、追加の拡張機能を持っています。新しいインターフェースの完全なリストについては、iOS6.0 API Diffを参照してください。

UIKitフレームワーク

  • UIステート保存が新たにサポートされました。UI State Preservationを参照してください
  • ビューは制約ベースレイアウトに新たに対応しました。 Auto Layoutを参照してください
  • UICollectionViewクラス(とそれをサポートするクラスとプロトコル)は、順序づけられた項目を表示することをサポートします。Collection Viewsを参照してください
  • UITextViewとUITextFieldは新たにアトリビュート文字列を使ってスタイル付きのテキストをサポートしました。
  • UIKitは新たにNSAttributedStringとNSMutableAttributedStringオブジェクトの描画をサポートしました。そして文字列ベースのビューは新たにコンテンツのためにアトリビュート文字列をサポートしました。
  • アクセシビリティは新たにボイスオーバーを拡張しました
    • ボイスオーバーは特定のアクションをトリガーにする新しいジェスチャーが使えるようになりました。
    • 開発者は、要素から要素へ、より論理的なフローを提供するためにボイスオーバーが生成する要素のリストの項目を一括で並べることができます。
    • スクロールビューは新たにスクロール中に特別な状態の文字列を提供できるようになりました。
    • アプリは新たに、アクセシビリティシステムがアプリのレイアウトやUIが変化したことを知るために通知を送ることができるようになりました。
    • 新しい通知はボイスオーバーのお知らせの状態についての情報を提供します。
  • UIActivityViewControllerクラスはメールやTwitterFacebookのようなサービスを通じてコンテンツをシェアするための機能が追加されました。Social Frameworkを参照してください。
  • UIDeviceクラスはベンダー固有IDが追加されました。
  • UIImageクラスは新たに、画像の拡大縮小率を指定するための初期化メソッドが追加されました。
  • アピアランス(見た目)のカスタマイズは、UIBarButtonItemやUIPageControlやUIPageViewControllerやUISwitchやUIStepperクラスにも追加されました。
  • UITableViewクラスは以下の変更があります。
    • 新しいUITableViewHeaderFooterViewクラスはテーブルのヘッダとフッタを実装します。
    • セルや他のビューの生成と管理をより簡素なモデルにしました。
  • ストーリーボードは、アプリがセグウェイの巻き戻しと復元IDの指定ができるようになりました。
  • UIWebViewクラスは、WebコンテンツのインクリメンタルレンダリングをOFFにする方法が提供されました。
  • UIViewControllerクラスは次のような動作をサポートする新しいインターフェースが追加されました。
    • 新しいインターフェースは画面の回転を管理したり追跡したりする分かりやすいパスが提供されました。
    • アプリはトリガーされるセグウェイを拒否できるようになりました。
    • セグウェイの巻き戻しが追加されました。
  • UINavigationBarをサブクラス化して、アプリのナビゲーションUIにカスタムナビゲーションバーを協調させることができるようになりました。

UIKit frameworkのクラスについて詳細な情報は、UIKit Framework Referenceを参照してください。


かみやん:ベンダー固有IDされましたね。UDIDが非推奨になった代わりでしょうね。同じディベロッパー内ならデバイスを一意に識別できると。

OpenGL ES

OpenGL ESは次の新しい拡張があります。

  • GL_EXT_texture_storage拡張は、1コールでテクスチャの全体の構造を指定することができ、アプリのテクスチャをOpenGL ESによってさらに最適化できます。(訳注:テクスチャフォーマットの変更ということか?)
  • GL_APPLE_copy_texture_levels拡張は、GL_EXT_texture_storage拡張の機能の上に構築され、テクスチャのミップマップ集合をテクスチャからテクスチャへコピーできます。(訳注:ミップマップごとコピーということか)
  • GL_EXT_map_buffer_range拡張は、バッファオブジェクトの一部だけを変更する必要があるときにパフォーマンスを改善します。
  • GL_APPLE_sync拡張は、アプリにファインゲイン同期を提供します。これはアプリが送るOpenGL ESコマンドの一部を選択したり、それらのコマンドが完了するまで待ったりすることができます。(訳注:CPUとGPUで同じメモリにアクセスなどのための同期機構らしい)
  • GL_EXT_shader_framebuffer_fetch拡張は、OpenGL ES 2.0アプリにのみ有効で、フラグメントシェーダーでフレームバッファのデータの読み取りをサポートします。

これらの拡張は、iOS6が走るすべてのデバイスで有効です。いつものようにアプリで使う前に拡張があるか確認してください。


かみやん:これは嬉しい!フラグメントシェーダーではフレームバッファは出力先でしかなかったが読み取りもできるようになった。

メディアプレイヤーフレームワーク

MPVolumeViewクラスは音量ビューの見た目をカスタマイズする新しいインターフェースが追加されました。アプリはボリュームスライダーとルーティングボタンに関連付けられた画像を変更するためのインターフェースを使うことができます。
MediaPlayerフレームワークのクラスの情報について、詳細はMedia Player Framework Referenceを参照してください。

Image IO フレームワーク

Image IO framework(ImageIO.framework)は、画像のEXIFIPTCメタデータにアクセスする方法が追加されました。アプリはCGImageSourceRefとCGImageDestinationRefのオパキュータイプに関連した関数を使ってメタデータにアクセスできます。
Image IO frameworkのクラスについての情報の詳細は、Image I/O Reference Collectionを参照してください。

iAdフレームワーク

iAd framework(iAd.framework)は、iPadバイス用にミディアム矩形サイズを新たにサポートしました。iAd frameworkのクラスについての情報の詳細は、iAd Framework Referenceを参照してください。

Foundationフレームワーク

Foundation framework(Foundation.framework)は次の拡張が含まれます。

  • NSFileManagerクラスはiCloudの利用可能判定のためのubiquityIdentityTokenメソッドが追加され、また、iCloudにユーザがログインしたりログアウトしたりアカウントが変更されたことを伝えるためにNSUbiquityIdentityDidChangeNotification通知が追加されました。
  • NSUUIDクラスは様々なタイプのUUIDを表現するオブジェクトを生成するのに役立ちます。例えば、アプリはRFC4122ver.4のランダムなバイト列としてNSUUIDオブジェクトを作成したり、既存のUUID文字列をベースにしたりすることができます。
  • NSURLRequestクラスは携帯電話ネットワーク経由のリクエストを許可すべきかどうかアプリから指定できます。
  • NSStringクラスは、すべて大文字へ、すべて小文字へ、単語の先頭大文字への変換メソッドが追加されました。

Foundation frameworkのクラスについての情報の詳細は、Foundation Framework Referenceを参照してください。

外部アクセサリフレームワーク

External Accessory framework(ExternalAccessory.framework)は、Bluetoothバイスへの接続を管理するための新しいインターフェースが含まれます。アプリはペアリングのために有効なBluetoothバイスのリストのアラートパネルを表示することができます。自動接続しない以前にペアリングされたアクセサリーを起こす機能が提供されます。
External Accessory frameworkのクラスについての情報の詳細は、External Accessory Framework Referenceを参照してください。

Event Kitフレームワーク

Event Kit fremework(EventKit.framework)は次の拡張を含みます。

  • フレームワークは、リマインダの生成をサポートします。Remindersを参照してください。
  • カレンダーとリマインダのイベントは新しくサーバ上の同じイベントを複数のデバイスで参照するための外部識別子を提供します。
  • iOS6以降にリンクしたアプリのために、システムはカレンダーやリマインダデータにアクセスする前にユーザに許可を取ります。
  • アプリは新たにEKEventEditViewControllerオブジェクトからプログラム的に編集をキャンセルさせることができます。

Event Kit frameworkのクラスについての情報の詳細は、Calendar and Reminders Programming Guideを参照してください。

Core Video フレームワーク

Core Video framework(CoreVideo.framework)は2つの新しいピクセルフォーマットに対応しました。これらのフォーマットは、OpenGL ESと協調する1チャンネルと2チャンネルの画像のための効率的な保存方法を提供します。Core Video frameworkの関数についての情報の詳細は、Core Video Framework Referenceを参照してください。

Core Media フレームワーク

Core Media Framework(CoreMedia.framework)は基本メディアタイムサービスビヘイビアとして定義されたCMColockRefとCMTimebaseRef型を追加しました。Core Media frameworkの関数についての情報の詳細はCore Media Framework Referenceを参照してください。

Core Location フレームワーク

Core Location Framework(CoreLocation.framework)は次の変更を含みます。

  • CLLocationManagerオブジェクトは新たにユーザが長い間行っていない場合にロケーションイベントの配信を一時停止します。この動作はフレームワークGPSや他のハードウェアをOFFにすることによってバッテリーを節約します。これはデフォルトで有効ですが、CLLocationManagerオブジェクトのpausesLocationUpdatesAutomaticallyプロパティの値を変更することでこのバッテリー節約機能をOFFにできます。アプリは、CLLocationManagerのactivityTypeプロパティの値を適切な値に設定することで使用方法を基にした位置の精度の向上ができます。このプロパティは現在、車での移動とフィットネス(訳注:散歩とかジョギングと思われる)を区別させることができます。

Core Location frameworkのクラスについての情報の詳細は、Core Location Framework Referenceを参照してください。

Core Bluetooth フレームワーク

Core Bluetooth framework(CoreBluetooth.framework)は、ペリフェラルモードでBluetoothバイスと通信することをサポートします。いままでiOSバイスはセントラルモードでのみ通信できたが、新たにiOSバイスペリフェラルモードで自分自身をアドバタイズ(訳注:発見される側として待つ状態)でき、他のiOSバイスと通信できる。
Core Bluetooth frameworkのクラスについての情報の詳細は、Core Bluetooth Framework Referenceを参照してください。


かみやん:Bluetooth4.0では省電力モードができ、ボタン電池で1年動作するBluetooth対応小型機器等が作れるようになり、それにより腕時計や脈拍計などの機器が発売されてきている。これらのデバイスペリフェラルバイス(周辺機器)と呼び、親側をセントラルデバイスと呼ぶ。ここでは、今まではiOSバイスがセントラルデバイスのみであったが、これからはペリフェラルバイスとしても動作させることができるということを指しています。

Core Audio

Core Audioフレームワークファミリは次の変更を含んでいます。

  • 比較的長いタイムスライスを使用して、優先順位の低いスレッドで処理することができる新しいAUDeferredRendererオーディオユニットができました。
  • AudioQueueProcessingTapオーディオユニットは、オーディオキュー内のデータに割り込み、それを処理することができます。

iOSで利用可能なオーディオ技術についての情報の詳細は、Multimedia Programing Guideを参照してください。新しいオーディオユニットについての情報の詳細は、Audio Unit Component Services Referenceを参照してください。

AV Foundation フレームワーク

AV Foundation framework (AVFoundation.framework)は、次の拡張を含みます。

  • AVPlayerクラスは外部のタイムソースに再生を同期するための機能が追加されました。シングルプレイヤーオブジェクトは、HTTPライブストリームおよびファイルベースアセット(プログレッシブダウンロードされているファイルとローカルファイルの両方を含む)の両方を再生することができます。そして、アプリは同じプレイヤーオブジェクトのビデオ出力を表示するために複数のAVPlayerLayerオブジェクトを使用することができます。
  • 新しいAVPlayerItemOutputクラスは、アプリがそれらを処理できるように、再生中に復号化されたビデオフレームをAVPlayerItemオブジェクトとして取得することができます。
  • AVAssetResourceLoaderクラスを使用すると、アセットのロード処理にデリゲートオブジェクトを挿入して、カスタムリソースリクエストを処理できるようになります。このクラスは、AVURLAssetクラスと共に動作します。
  • AVAudioSessionクラスは新しく使用中の現在のオーディオルートについての情報を公開します。
  • AVAudioMixInputParametersクラスは、オーディオデータが再生され、読み込まれ、公開される前にそのデータにアクセスするために使われるMTAudioProcessingTapRefデータにアクセスする機能を提供します。
  • AVAudioSessionクラスは次の拡張を含みます。
    • 割り込みや変更を検出するためのオーディオセッションデリゲートの使用は、新しい通知セットにより使用非推奨になりました。
    • オーディオセッションは複数のオーディオハードウェアデバイスのルーティングを許可する新しいAVAudioSessionCategoryMultiRouteオーディオカテゴリをサポートします。
    • オーディオセッションは、ムービー再生のシナリオのための適切な出力信号処理をする新しいAVAudioSessionModeMoviePlaybackモードをサポートします。
  • AVCaptureConnectionクラスは、アプリでビデオ受信においてビデオの安定化のための機能をON/OFFすることができます。
  • AVCaptureMetadataOutputクラスは、キャプチャコネクションオブジェクトによって発行されたメタデータに割り込むための新しいクラスです。
  • AVMetadataFaceObjectはAVCaptureMetadataOutputオブジェクトによってキャプチャされた顔認識の報告をする新しいクラスです。
  • AVTextStyleRuleクラスは、字幕やクローズドキャプション(訳注:ON/OFFできる字幕)や他のテキスト関連メディアアセットのためのオプションのスタイルを指定するための新しいクラスです。
  • AVAudioPlayerクラスは、MPMediaLibraryクラスから取得したURLを使ってユーザのiPodライブラリからオーディオアイテムを再生することができます。

AV Foundation frameworkのクラスについての詳細な情報は、AV Foundation Framework Referenceを参照してください。

Ad Support フレームワーク

Ad Support Framework ( AdSupport.framework )は、広告目的でアプリが使うことができるIDにアクセスする機能を提供する新しいフレームワークです。このフレームワークはまた、ユーザがアドトラッキングにオプトアウトしているかどうかを示すフラグも提供します。アプリは広告IDにアクセスする前にオプトアウトフラグをみて、それに従う必要があります。
このフレームワークについての情報の詳細は、Ad Support Framework Referenceを参照してください。

Accelerate フレームワーク

Accelerate Framework( Accelerate.framework )は、新たにベクタスカラパワー関数、vDSP関数、離散コサイン変換関数、SSE関連ベクター関数、sine関数、vImage関数が追加されました。
Accelerate frameworkの関数についての詳細は、Accelerate Framework Referenceを参照してください。

さいごに

やー、iOS6iOS5からOTAでアップデートできるようになったので、普及は早そうですね。早めにiOS6専用アプリとかを新規に作ってもよさそう。楽しみ楽しみ。
個人的にはOpenGL ESの新機能を触って調べてみたい。
――
よかったらibisMail Free(無料)、ibisPaint X(無料)ダウンロードをよろしくお願いします。
URL:ibisMail Freeのダウンロード、レビューはこちら
URL:ibisMail for iPhoneのダウンロード、レビューはこちら
URL:ibisMail for iPadのダウンロード、レビューはこちら
URL:ibisPaintのダウンロード、レビューはこちら サイトは、ibispaint.com
ご意見ご要望はTwitterから: @kamiyan

iPhone Dev勉強会 で発表しました。

iPhone_Dev_jp 第1回勉強会

2012/4/14 にiPhone_dev_jpの第1回勉強会が開催されたので、発表してきました〜。
ATNDは http://atnd.org/events/26946 こちら。



発表に使ったスライドを貼っておきます。ibisPaintでのOpenGL ES2.0利用についての発表です。
他の方の発表で、よかったのは

  • @nakamura001さんのUnityの開発キットのデモは、「なるほど、これは高機能、簡単に作れそう、Unityからフラグメントシェーダーも書けるのか〜」という感じ。
  • @akisutesamaさんの超ハイテンションのプレゼンで、会場を沸かせるテクニックは、すばらしい。あきすてメソッド!という感じ。
  • @sonson_twitさんの iCloudiCloudはなかなか大変そう〜。
  • @fladdictさんのパン・ズーム・回転の実装の仕方の発表は、技術的にはibisPaintでも実装済みなので知っていることであったけど、プレゼンの仕方が非常に分かりやすい、さすがfladdictさんという感じ。

iPhone_dev_jp第2回勉強会

で、2012/05/26にiPhone_dev_jp第2回勉強会があったのですが、募集から2時間ぐらいで速攻満席で、発表者なら受け付けるということで、第2回も発表しました。
ATNDは http://atnd.org/events/28693 こちら。



第1回の私の発表は難しかった〜という声がTwitterに流れていたので、フラグメントシェーダーを使ってみて〜。という発表。今回は発表用にアプリを作ったのでソースコードも公開しました。BSDラインセンスで公開するので使ってください。
他の発表者で面白かったなと思ったのは、

  • @k_katsumiさんのARC。さすが岸川さん。超詳細。結構はまりどころがあるARC。でも便利。
  • @fladdictさんのUITableViewハック。横にスクロールしたり、カバーフローっぽくしたり。相変わらず、プレゼンが分かりやすい。
  • @cqa02303さんのAirPrint。AirPrintを使う側の話かと思ったらIPP(Internet Printing Protocol)をバイナリハックして仮想プリンタを実装したとか。低レベルでステキ。
  • @cathandnyaさんのWebViewのcontentEditable。これはうちの社内でもibisMailのために研究していたことなので、内容は知っていることだったけど、同じくキーボードを自動で出す方法が見つからない。と。うちもそれでibisMailに採用するのを見送った。iOSのバージョンアップで制御できるようになるといいな。
  • @takiuchiさんのカメラの輝度調整。APIが不足しているので、補足して実装した。アイデアがよいね。そしてさらっとOpenGL ES使い。

たまには発表もいいですね。多くのハッカーに会えたし。
ーー
この記事が参考になったという方は、ibisMail Free(無料)、ibisPaint X(無料)でよいのでダウンロードをよろしくお願いします。
URL:ibisMail Freeのダウンロード、レビューはこちら
URL:ibisMail for iPhoneのダウンロード、レビューはこちら
URL:ibisMail for iPadのダウンロード、レビューはこちら
URL:ibisPaintのダウンロード、レビューはこちら サイトは、ibispaint.com
ご意見ご要望はTwitterから: @kamiyan