Hatena::ブログ(Diary)

Kazzzの日記 このページをアンテナに追加 RSSフィード

2013-08-31

[][]モバイルデバイス後方互換性と今更iPodを買うということ

Apple iPod touch 32GB 第5世代 ホワイト&シルバー MD720J/A

Apple iPod touch 32GB 第5世代 ホワイト&シルバー MD720J/A

今更iPod Touchの現行モデル(5G)を買った。 新しいiPhoneの噂が毎日聞えてくるといういうのに。

というのもXcodeiPhone4/iPod用に書いたアプリケーションiPhone5で動かなかった(EXC_BAD_ACCESS)ので、その検証に使えるiOS6.xデバイスが欲しかったからだ。
新しいiPod Touch 5Gが出たときに、メモリが増えた、カメラオートフォーカスが付いた、ディスプレイが4inch Retinaになった等、スペックは当時のiPhone4Sとほぼ同じか一部超える部分もあり、興味は惹かれたのだが、まあ、現在のモデル(4G)でも暫くはなんとかなるだろう、と考えていたのが甘かった。

Androidバージョンが多岐にわたり分断が激しいとか、機種依存が激しいとか巷でよく言われるが、それを見越した設計がされており、OSそのものの後方互換は高く、例えばAndroid2.1時代に開発したアプリケーションは画面サイズやマニフェスト等に気を付ければ最新の機種でも大抵はそのまま動く。これは素晴らしいことだと思う。

現在、私はGALAXY Nexusを使っているがこれは既にかなり古い機種と呼んでも良いだろう。 Nexusの冠のおかげで最新のOS(現在はAndroid4.3)のROMイメージ提供されるものの、購入してから今まで開発機としてずっと現役であり、Android2.xの時代に開発したものから最新のまで動作するアプリケーションの範囲は広い。

そんなAndroidと同じ感覚iOSデバイスを使っていたのだが、iOSデバイスは基本、2世代を超えた旧機種はサポートされず、動かないアプリがあっても仕方なしとされるようで、開発者はそれを見越してデバイスを買い増していかなくてはならないようだ。

Appleは恐らくはiOS7を搭載した新しいiPhoneを9月に出すだろう。これが次のメインストリームとなるわけで、次のiOS8が出る頃にはまたデバイスを買い替えなくてはならないことを意味している。

2013-08-25

[]設定したアプリケーションバージョンを取得する

いや、別にバージョンじゃなくて以前に書いたような定数を返すような関数でも良いのだが、決められた又は生成されたオブジェクトを返すモジュールスコープのオブジェクトが欲しい場合、以下のようにAngular.Moduleのvalueメソッド定義することができる。

'use strict';
var modules =  angular.module('Hoge', ['ngRoute']);
//アプリケーションのバージョンを返す
modules.value('AppVersion', 1.0);

これをコントローラ中から使うには以下のように書けばよい

  • controllers.js
'use strict';
function HogeController($scope, $log, AppVersion) {
    $scope.version = AppVersion;
    $scope.$log = $log;
    $scope.$log.log('+++++ application version = ' + version );
}

これは簡単だ。

2013-08-24

[]久しぶりに"Type R cannot be resolved to a variable"にはまる

もうなにをしても駄目なのだが、エラーが出ているクラスのRのインポートをやり直しを実施した所、エラーは最終的に一つのクラスに収束した。
そのクラスだけは何をしてもエラーが取れなかったのだが、最後ソースコードを一旦削除して作り直した所、漸くエラーが取れた。一体なんだったんだろう。本当、このエラーだけは何度やってもいつかまた出る訳でイライラする。

プロジェクト配下のリソースコンパイル結果が格納されるgen配下のクラスが原因で起こる"Type R cannot be resolved to a variable"は、かなり前のSDKから出ている現象なのだが、未だに根治されていない。Logcatゴミフィルタを設定しないと未だに消えない。
そろそろeclipse限界なのかもしれんなぁと思うのだった。

2013-08-18

[]eventual consistency

にも関わらず、直後に問合せしても登録されたはずのデータgetで返ってこない。数100ms〜1秒程度遅れるようだ。
まだ調べているが、どうやらデータそのもののputが完了しても、その後非同期に他の処理を行っており、これが完了しないとgetの一覧には載ってこないのだと思われる。 うーん。なんだろ。

調べてみたがこの事象所謂"eventual consistency"と呼ばれる一貫性のポリシであり、これは現在のGoogle App Engineプラットホームで使用しているBigtableではデフォルトの振舞いのようだ。
同プラットホームのように分散環境で使用するデータベース、データストアにおいてはConsistency、つまり一貫性の即時性を多少犠牲にしてもAvailability(可用性)とPartition-tolerance(分断耐性)を優先していることなのだろう。※

この辺はGoogleのドキュメント最適化の資料でも説明されている。
Datastore Queries/Google App Engine — Google Developers

Non-ancestor queries are always eventually consistent.

祖先クエリではないクエリは常時eventually consistentである

Optimizing Your App Engine App - Google IO 2012

App Engine Pattern #10
Why?
A global query can get stale results.
Multiple run of the same query can get different results.
Use ancestor query to get strong consistency.
Use keys_get & get_multi to trade index consistency for write throughput.

グローバルクエリは結果を取りこぼす
同一のクエリを開く数回実行しても違う結果が返る
祖先クエリはstrong consistency
...略

つまりは、Google App Engine/Datastoreにおいて、insertした直後に普通に問合せしたEntityの取りこぼしは当たり前だということである。内部ではDatastoreへのputが完了していても非同期でインデクスの更新等が遅れており、すべての更新がEntityに反映するまでに時間差があるのが原因ぽい。(これがAncestorクエリでは強い一貫性が保持されるっていう原理がよくわからんのよね)

となれば、方法は二つで、

    • eventual consistencyが発生するのを受け入れてシステムを考える
    • eventual consistencyができるだけ発生しないように工夫する

このどちらかを考えなくてはならない、ということだ。

※この3つの要素のは分散処理のデータ処理におけるトレードオフであり、「CAP定理」として一般化されている。
CAP定理 - Wikipedia

2013-08-17

[][][]DataStoreにputされたオブジェクトが直後のgetに含まれない

フロントエンドをAngularJSで書いてサーバーにはjsonengine(Slim3)を叩くプロトタイプを書いているのだが、腑に落ちない点がある。
jsonengineはRESTを受け付けるようにできており、jsonオブジェクトの問合せ(query)と保存(save)はAngularJSの$resourceをサービスとして書ける。

services.js
var services = angular.module('Hoge.Services', ['ngResource']);
services.factory("HogeProject", function($resource) {
    var Project = $resource(
        '/hoge/project?_docId=:projectId', {}, {
            query: {
                method: 'GET'
                , params: { projectId: '@projectId' }
            },
            save: {
                method: 'POST'
                , params: { projectId: '@projectId'}
                , data: { _doc : '@_doc'}  // StrigifyしたJSONをセット
            },
        });
    return Project;
});

呼び出すには以下のようなコードとなる。

HogeProjectサービスを呼び出すHogeController.js
function HogeController($scope, $route, $routeParams, HogeProject) {
   $scope.insertProject = function() {
        HogeProject.save({projectId : '', _doc: JSON.stringify($scope.formData)}
            , function(data) {
                //成功時
                //リロード
                $route.reload();
            }, function(responce) {
                //失敗時
            });
    };
}

insertProjectを呼び出すHTML
<form ng-contoller="HogeController">
    <button type="submit" ng-click="insertUdateProject()">登録</button>
</form>

中身はAjax Request/Responseだが随分とスマートになったもんだ。
まあそれはいいんだが、このようにしてjsonengineに保存したデータが、すぐには問合せの対象にはならないのである

jsonengine側のコード(CRUDService.javaより抜粋)
public String put(CRUDRequest jeReq, boolean isUpdateOnly) {
    final Transaction tx = Datastore.beginTransaction();
    JEDoc jeDoc = getJEDoc(tx, jeReq);
    Datastore.put(tx, jeDoc);
    tx.commit();
}

サーバー側、jsonengineは内部ではSlim3のDatasourceクラスのputをトランザクション中で使っているが、内部的にはローレベルAPIを叩いており速度的には問題はないはずだ。
実際計測してもputに数ms〜数10msしかかかっておらずコミットも正常に完了している。

にも関わらず、直後に問合せしても登録されたはずのデータがgetで返ってこない。数100ms〜1秒程度遅れるようだ。
まだ調べているが、どうやらデータそのもののputが完了しても、その後非同期に他の処理を行っており、これが完了しないとgetの一覧には載ってこないのだと思われる。 うーん。なんだろ。

2013-08-11

[]Live Editing時にスクリプト更新が反映されない場合

f:id:Kazzz:20130811191846p:image
WebStormはChrome等のサポートされているブラウザ拡張と共に"Live Editing"機能をオンにしておくことで、デバッグ時にWebStorm側でHTML、CSS、Javascriptを変更すると、その結果が即座に反映される。

この機能はすごく便利なのだが、なぜかJavascriptの変更が反映されない場合がある。 この時、WebStorm側からデバッグを停止、再開することで変更が反映されそうなものだが、Webブラウザ側のキャッシュのせいか何度デバッグを開始しても反映されないケースがあり、いらいらしてしまう。

色々試して分かったのだが、WebStormとWebブラウザ(拡張)とデバッグ時の通信が確立した以降は、変更が即座に反映されない場合でもデバッグを止めることはできるだけせず、Webブラウザのリロードを実行することで変更が反映されるのを待つ方が上手くいくことが多いということだ。

一度同期が外れると中々復活しない原因は分からないのだが、WebStormとWebブラウザ拡張が通信を開始した時の状態(同期した時点)辺りに原因がありそうな気がする。

2013-08-10

[][]他の要素に変更を通知する

AngularJSは素晴らしいフレームワークでありJavascriptのイベント処理を意識することはあまりないのだが、それでも皆無ではない。
私がそれを必要としたのは、特定の要素が変更されたことを他で検出したいケースだ。

AngularJSはコンテナとなる要素、例えばdiv要素毎にコントローラを配置できる。コントローラは要素と同じ親子関係を持つが、基本的にはスコープとして他と分離、隔離されており互いに影響を与えない設計となっている。 ※
これ自体は非常にスマートで理にかなった設計なのだが、分離されているが故に他の要素を変更を検知するためには仕掛けが必要になるケースがある。

$scope.$broadcast

接頭に$が付くものはAngularJSが使用する予約された変数だが、そのうち$scopeは最も多用する変数であり、コントローラが定義されたスコープ(要素)に対してAngularJSが必要なオブジェクトへの参照をセットして提供される。この$scopeが持つ、これまたAngularJSが提供する$broadcast関数はその名の通り、自身の階層化の$scope全てひいてはコントローラに同報を送信することができる。

ブロードキャストを同報するのは簡単だ。第一パラメタはこのブロードキャストを識別する名前、第二パラメタはブロードキャストを受信する側に送るデータをセットする

parentController.js
function ParentController($scope, $broadcast, $log) {
    //何かの処理で要素の変更を実施
    $scope.$broadcast('elementChanged', data);
}

配下のコントローラでは$scope.$on関数を使ってブロードキャストをハンドルすることができる。

childController.js
function ChildController($scope, $log) {
    $scope.$log = $log;
    $scope.$on('elementChanged', function(event, data) {
        $scope.$log.log('++++ receiveing broadcast');
    });
}

これで、例えば非同期でサーバからデータを取ってきた後にform要素を変更した結果を配下の要素にスマートに伝達することができる。

このような処理を実装する場合、直接他の$scopeに手を突っ込んで変数を見る、グローバル変数を定義してそれを共有する等の昔からある方法もあるが、そんな泥縄な処理を書きたくないからAngularJSのようなフレームワークを使うのであって、絶対に使いたくないのだ。

※AngularJSを使い始めてまだ日は浅いが、Controllerの粒度重要で、HTMLの文書構造を上手く集約したコントローラを複数用意するのが重要だと感じている。

2013-08-04

[][]spin.jsのプログレスインジケータを常時センターに表示する。

先日紹介したプログレスインジケータだが、HTML中に記述したdiv要素を使用するため、他の要素との関係スタイルによっては期待した位置に表示されないことがある。

問答無用でWebブラウザのセンター付近に表示するには色々やり方がありそうだが、一番簡単なのはCSSで直接属性を設定することだ。

spin.jsのプログレスインジケータ用要素をセンターに表示する
<div id="progressIndicator" style="position:fixed;top:50%;left:50%"></div>

これでOK。

見た目が分離されているCSSはこういうケースでは凄く便利だ。

2013-08-03

[][]spin.js integrated AngularJS

問題はAngularJSっぽく実装したいということだ。ディレクティブ? サービス? どうしようか。

調べた所、既にサービスとして実装している例があったので、これを有難く使わせて頂くことした。
Adding a weel progress indicator to your AngularJS application - xvitcoder blog

AngularJSのサービスとして組み込まれたspin.js
var services = angular.module('MyServices', ['ngResource']);
services .factory('ProgressIndicator', function() {
    var opts = {
        lines: 11, // 描かれる線の数
        length: 30, // それぞれの線の長さ
        width: 15, // 線の幅
        radius: 40, // 内円の半径
        corners: 1, // 縁の丸さ
        rotate: 0, // ローテーションオフセット
        color: '#000', // #rgb又は#rrggbbで色を記述
        speed: 1.4, // 描画速度
        trail: 100, // 残像光の割合
        shadow: true, // 影を描画するか
        hwaccel: true, // 可能であればハードウェアアクセラレーションを使うか
        className: 'spinner', // CSSクラス名を指定する(他から参照するため)
        zIndex: 2e9, // Z-index (デフォルトは2000000000)
        top: 'auto', // 描画を開始するY軸座標の位置
        left: 'auto' // 描画を開始するX軸座標の位置
    };
 
    var target = document.getElementById('progressIndicator');
    var spinner = new Spinner(opts);
 
    return {
        startSpinner: function() {
            $(target).fadeIn('fast');
            spinner.spin(target);
        },
        stopSpinner:function () {
            $(target).fadeOut('fast');
            spinner.stop();
 
        },
        getSpinnerTarget:function () {
            return target;
        }
    };
});

使い方は簡単だ。例えばAngularJSのコントローラーからならば、以下のようにProgressIndicatorをインジェクションして使う。

controller.js
function HogeCtrl($scope, $routeParams, $http, Hoge, ProgressIndicator) {
    $scope.ProgressIndicator = ProgressIndicator;
    ProgressIndicator.startSpinner(); //描画開始
    //
    なにか時間のかかる処理
    //
    $scope.ProgressIndicator.stopSpinner(); //描画停止
}

HTMLにはprogressIndicatorという名前IDを持った要素を追加しておく必要がある。

    <div ng-view></div>
    <div id="progressIndicator"></div>

実行結果 (描画時)

f:id:Kazzz:20130803134713p:image

注意すべきなのは$resourceや$http等の通信の場合、succcessやerror等のコールバック関数は処理が非同期に実行されるるため、インジケータの停止は関数呼び出しのコンテキストではなく、コールバック関数の処理完了時に行わなければならない、ということだ。

Connection: close