RubyHiroba2014のLTについて

RubyHirobaのLTについていくつかお話できることを。

結果的につまらないプレゼンで時間をとってしまって、さらに運営に余計な対応をさせるはめになってしまったことはホント申し訳ないと思っております。

今回話題が公になったので、引き続き議論していただくための参考資料として、僕の側から見て何が起きていたのかを説明させていただきます。

前日まで

 RubyMLのメールでLT募集のことを知りました。

[ruby-list:49953] 9/21(日) RubyHiroba2014の参加受付中です

 これは面白いんじゃないかな、と思って、今回のようなLTをやろうと思っている旨を当人に相談した所、OKが出たので、LTthon | RubyHiroba 2014を見て、応募フォームからタイトル書いて申し込みました。この時、アンチハラスメントポリシーにはぜんぜん気付かなかったです。わりとギリギリの話題をやろうとしている自覚はあったので、気づいていたら止めてたと思います。

 LT申し込みフォームにタイトル記入欄があったから、内容が明快にわかるように「社長が美人で何がわるい」というタイトルで申し込みました。ダメならここで止めてくれるだろうと思ってました。
 結果的には、工数がないからノーチェックで通ってしまったのだそうです。ボランティアだからそうなることは仕方ないと思うのですけど、この時点でそんな可能性はまったく思いつかなくて、当日の案内メールが来たことで「あ、OKなんだな」と理解してしまいました。

当日

 一人目の人が欠席で、結果的にぼくがトップで発表することになりました。

 発表が*終わってから* 「ところでRubyHirobaにはアンチハラスメントポリシーというのがあります」といわれて、ドキっと思わなくもなかったのですが、その後「PHPをDisったりするのは禁止です」というから、「あ、これは場を盛り上げるためにぼくのネタを引き継いで冗談を言っているのだな」と理解しました。

 その後も、Tシャツ取りに行った時とか、「ここで食事しても大丈夫ですか?」ってスタッフの方に確認に行った時とか、どこかのタイミングで直接言っていただけたら、記事の公開を見合わせるとかもありえたのですけど、上述の通り冗談をいわれていると思っていたので、ぎりぎりオッケイなおもしろいプレゼンをしたつもりでいました。

 時間があればもう一個枠をいただいてRails+GoogleAnalysticsの話もやろうと思っていたのですけど、前日から体調不良気味だったし、午後になって枠がいっぱいになっていたので、無理せず帰宅しました。ひょっとしたら運営の方は、終わってからお話をしたかったのかもしれないですね。

月曜日

 土曜日のRuby関西同窓会で、LTと同様の話をしてスカウトした方がオフィスに来てくれたので、僕としては成功したつもりになってました。
そこで、資料に出ている当人にプレゼンを見せて、「公開してもいい?」と聞いたところ、「いいよ」とのことだったので、ノリノリで資料も公開しました。

半日後くらいにRubyHiroba運営の方からメールを頂いて、ここで初めて事態を悟りました。
指示に従って資料は非公開にして、連絡を待っていたのですが、今日になって、「RubyHirobaの
アンチハラスメントポリシーに明確に抵触するものではない、という結論になりました」というメールを頂きました。WEBサイトで「残念ながら、ジェンダーや性的な表現について、アンチハラスメントポリシーに抵触する発表がありました」と書かれていることとの整合性については、僕にはよくわからないです。

LTthonで行っていただいたプレゼンですが、運営で協議したところ、RubyHirobaの
アンチハラスメントポリシーに明確に抵触するものではない、という結論になりました。
曖昧な言い方で大変申し訳ないのですが、「あのようなポリシーを掲げているイベント
での発表としてはあまり好ましくない。しかし、どこがダメで誰に対してのハラスメント
であるかを明確にすることが難しい」という判断です。

ただ、ポリシーに抵触するのかしないのかはさして重要ではなくて、くだらないLTのために運営に余計な手間を取らせてしまったことはほんと申し訳ないし、調子のってたなーと思っております。以後気をつけます。

respond.jsが効かないもうひとつの理由

modern.ieでIE8の動作確認していて、昨日まで動いていたはずのデザインが突如崩壊した!リリース直前なのに!respond.jsが効いてない!?なんで?
コードを戻してみたり、「respond.js 効かない」でググってみても直らない。迫るリリース時刻!電車の動き出す音!
って時は、そのVirtualMachineが外部ネットワークに接続できるかどうか確認することをおすすめします。CDN上のrespond.jsはネットワークにつながってないと当然動きません。
ホストマシンのrailsでテストしていると意外と気づかないので、パニックになった時は一度ご確認を。
・・・あー。びっくりした。

swap領域の把握にはsar -Sを使う

「sar -r」でメモリと一緒に把握するように書いている記事が多いけど、それはsysstat7までの話で、sysstat8からはスワップメモリについて出力する”sar -S”というmetricsが追加されている。近年のlinuxマシンだったら、ほとんどは”sar -S”を使うことになるはず。

$ sar -r
Linux 3.12.9-x86-linode56 (linode6.mogya.com) 02/16/14 _i686_ (8 CPU)

00:00:02 kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit
00:10:01 28284 998736 97.25 90452 435064 582540 45.19
00:20:01 27424 999596 97.33 90720 435080 582388 45.18
00:30:01 25792 1001228 97.49 90920 435264 584120 45.31
00:40:01 60456 966564 94.11 93360 400776 582312 45.17
00:50:01 60024 966996 94.16 93560 400812 582492 45.18

$ sar -S
Linux 3.12.9-x86-linode56 (linode6.mogya.com) 02/16/14 _i686_ (8 CPU)

00:00:02 kbswpfree kbswpused %swpused kbswpcad %swpcad
00:10:01 254208 7932 3.03 2496 31.47
00:20:01 254208 7932 3.03 2496 31.47
00:30:01 254208 7932 3.03 2496 31.47
00:40:01 253928 8212 3.13 2492 30.35
00:50:01 253928 8212 3.13 2492 30.35

SYSSTAT Features
man SYSSTAT

sauceLabsをつかってブラウザテストを自動化する

SauceLabs2.jpg

Sauce Labsというサービスがあって、WindowsやMac、iOSやAndroid等の各種OS上の各種ブラウザを仮想マシン上に立ち上げて、ブラウザ上でブラウザを操作することができるサービスです。
当初これがあればWindowsマシンを持ち歩かなくてもIE6のテストが出来るね!くらいの認識だったのですけど、説明を見ていると、どうやらSelenium WebDriver経由でユニットテストを実施することが本来の用途らしいので、コードをコミットしたら各種ブラウザでテストを実施してダメだったら教えてくれる環境というのを構築してみました。
selenium webdriverが登場した頃から、理屈の上ではできると言われていたテストですけど、あれこれの環境を個人で維持するのは現実的でないので、こういうのをサービスとして提供してくれるのは嬉しいですよね。

たとえばこういう、Google.comに行って検索した結果のページを表示するテストを実施するコードを用意して…

capsのところはSauce Labs: Supported Device, OS, and Browser Platformsからコピペすることが出来て、必要な設定に書き換えればMacのChromeでもWindowsXPのIE6でも、iOS7でもAndroidでも、任意のブラウザで同じテストを実施することが出来ます。

ということで、環境変数BROWSERでブラウザの種類を指定してunittestができる仕組みを作ってみました。

#Special thanks to Ruby-ML

通常の Test::Unit::TestCaseの代わりに SaucelabsTestCasePCを継承してテストを作れば、環境変数BROWSERで指定したブラウザでテストを実施することができるようになります。

$ env BROWSER=mac_chrome ruby pc/search.rb
Loaded suite pc/search
Started
.
Finished in 23.795709 seconds.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
Test run options: --seed 21266
$ env BROWSER=win_ie6 ruby pc/search.rb
:

テスト結果はunittestとして成功失敗を判定する以外にも、Saucelabs上ではこのようにキャプチャ画像や動画として保存されているものを見ることも出来ます。

SauceLabs7.jpg
SauceLabs6.jpg

ここまでできれば、あとはいつもどおりにunittestを書いてjenkinsさんにお願いしておけば、コードをコミットするだけで各種ブラウザの動作確認をしてくれるようになりますね。すばらしい。

ちなみにSauce Labsは月額課金のサービスで、月一時間位までは無料で使うことが出来ます。
1テスト30sくらいで実行できるので、全部のテストを一度通してみるくらいなら無料でも行けるけれど、本当にコミットするたびに全テスト走らせるのであれば、$12のManual会員か、$49のAutomated会員くらいは必要になるかもしれないですね。
今これを書きながら気づいたのですけど、githubに公開してあるコードは無料でテストできるらしいです。Sauce Labs: OSS

[titanium] regionChangedの抑制

Titanium™ Advent Calendar 2013 – Adventar の7日目は、大さん橋を見ながら@mogyaがお送りします。

1466042_10152052877289243_273095704_n.jpg

山下公園でコード書くとかかっこよくない?と思ったんだけど、寒くてコードどころじゃない感じでした…

さてさて、TitaniumにはMapViewというのがあって、これを使うと簡単に地図アプリが作れるのですが、GoogleMapsSDKのバージョンが上がった関係で、現在Androidでは標準のMapViewではなくてModules.Mapを使うように言われていて、ちょっと混乱期にあります。3.2.0で旧バージョンが削除されるらしいので、来年には落ち着くんじゃないかと思うのですけど。現行だと、こんなかんじで書かないといけません。

map_view.js

さらにさらに、MapViewを使って、地図をドラッグした時に発生するregionChangedイベント(これも最近名前がregionchangedイベントに変わりましたw)を契機としてモバイラーズオアシスAPIを呼び出すようにすれば、電源が使えるお店を一覧するアプリを作ることが出来ます。

実際作ってみるとわかるのですが、このつくりだと微妙に使い勝手の悪いアプリになります。ちょっと離れた位置を見ようとして移動を繰り返すと、画面がちょっと動くたびにデータをロードしに行くので、処理がたまってだんだん重くなってしまうのです。

ブラウザ上のGoogleMapsSDKで開発する場合には、処理が落ち着いた時に呼び出してくれるidleイベントというのが存在するので、これを使えば綺麗に実装することが出来ます。

[GoogleMapsAPI] APIと組み合わせる時のイベントはidleを使う – もぎゃろぐ

で、idleイベントが存在しないAndroidやiOSのSDKでどうするか、というのがこの記事のテーマです(前置き長すぎだろうw)

idleイベントと近そうなイベントとして、completeイベントというのがあって、これを使うのも一つのアイデアです。ただこのイベントは、地図のロードが完全に終わってから呼び出されるので、これで実装すると、「ユーザーがドラッグ→地図がロードされる→(一呼吸)→店舗が表示される」という、これまた使い勝手の微妙なアプリになります。スポット数が少なかったり、通信環境が安定していてAPIが高速に応答してくれていれば、余りばれないのですけど・・・

今年リリースした電源検索新バージョンや、iPhone版の電源検索では、regionChangedイベントでそのままAPIを呼び出さずに、キューにイベント情報を積んでおいて、一定時間regionChangedイベントが発生しなくなったら、最後の一回分だけAPIを呼び出す、という実装を行いました。

data_task.js

data_task.js部分が問題のロジックをカプセル化したものです。
regionChangeイベントが発生したらとりあえず全部requestでDataTaskにあずけてしまいます。

mapview.addEventListener(regionchanged,function(evt){
datatask.request(evt);
});

DataTaskは内部でタイマーを持っていて、一定時間が経つと、最後に呼び出したrequestの引数を使って、actionコールバックを呼び出します。

actionコールバックの中でAPI呼び出しを行うようにすれば、おおむね、ユーザーが地図をドラッグしおわったタイミングでAPI呼び出しを行うことが出来ます。

var taskAction = function(self,evt){
Titanium.API.info('taskAction '+evt.longitude+','+evt.latitude);
};
var DataTask = require('data_task');
var datatask = new DataTask({
interval:3000,
action:taskAction,
caller:this
});
datatask.start();

Dalvik_Debug_Monitor.png

書きながら「そこまで頑張る必要があったんだろうか。実はcompleteイベントでいいんだったりして」とちょっと不安になってきたのですけど・・・大丈夫なはずだ。神は細部に宿る!

明日はryugooさん@ChatWorkです。よろしくお願いしますー。

latlngの宗教問題

1406390_59235278.jpg

geocoderの話が予想以上にうけてびっくりしたので、ジオコーディングのくだらない話を一つ。

ジオコーディングに関するコードを書いているとたいていハマる問題が一つあって、それは緯度経度をどういう変数で表すか。
geo – Longitude の略し方。lng 派と lon 派の終わらない争い – Qiita
によると、だいたい世界には6つの派閥が存在するらしいです。

  • Lng派: lat,lng
  • Lon派: lat,lon
  • Long派: lat,long
  • Longitude(略さない)派: latitude,longitude
  • 緯度経度ペアに名前付けちゃう派: “34.12345,135.98765”
  • x,y派: latlngじゃなくてxとyで済ませちゃう人たち

個人的には、上記の他に、

[34.12345,135.98765]

とArrayで表す派閥が存在すると思っています。
例のRubyGeocoderは、もともと文字列を受け取ることになっていたのですが、最近Arrayも受け取るように修正が入ったようです。

ついでなので、Ruby界から、

{lat:34.12345,lng:135.98765}

と表す新Ruby派と、

{"lat"=>34.12345,"lng"=>135.98765}

と表す旧Ruby派なんかも参戦していただくと、問題が混沌としてきて、より宗教戦争らしくなってきますね。

609407_23691178.jpg

戦争を煽るばかりでは申し訳ないので、みんな仲良くできるコードを用意してみました。

緯度経度を表す多様なフォーマットを全部受け入れて任意のフォーマットで出力する試み
https://gist.github.com/mogya/6978002

これをつかえば、上記で上げたようなパターンをすべて扱うことが出来ます。

irb> GeoUtils::coordinates([34.12345, 135.98765])
=> [34.12345, 135.98765]
irb> GeoUtils::coordinates(["34.12345", "135.98765"])
=> [34.12345, 135.98765]
(以下、戻り値は略)
irb> GeoUtils::coordinates("34.12345", "135.98765")
irb> GeoUtils::coordinates(34.12345, 135.98765)
irb> GeoUtils::coordinates( {lat:34.12345, lng:135.98765} )
irb> GeoUtils::coordinates( {lat:34.12345, lon:135.98765} )
irb> GeoUtils::coordinates( {'lat'=>34.12345, 'long'=>135.98765} )
irb> GeoUtils::coordinates( {'x'=>34.12345, 'y'=>135.98765} )

出力側はgeocoderにあわせてarrayにしてあるけど、これも指定可能

irb> GeoUtils::coordinates([34.12345, 135.98765],:lat_lng)
=> {:lat=>34.12345, :lng=>135.98765}
irb> GeoUtils::coordinates([34.12345, 135.98765],:lat_long)
=> {:lat=>34.12345, :long=>135.98765}
irb> GeoUtils::coordinates([34.12345, 135.98765],:latitude_longitude)
=> {:latitude=>34.12345, :longitude=>135.98765}

こんなこともできます

irb> GeoUtils::coordinates(
[34.12345, 135.98765],
[:lat_lng,:lat_long,:lat_lon,:latitude_longitude,:x_y])
=> { :lat=>34.12345, :lng=>135.98765,
:long=>135.98765, :lon=>135.98765,
:latitude=>34.12345, :longitude=>135.98765}

使わなくて済んだはずのCPU時間を横着のために使うのはどうかという気もするのですが、気にならない方は自由に利用してください。

Ruby geocoderがすごい

住所を緯度経度に直したり、緯度経度から住所を求めたりする操作をgeocodingと言って、Google Maps APIを使うとまあたいていのことはできる。
ロケタッチAPIとか、Yahoo!ジオコーダAPIという手もある。

それはともかく、そのへんをパチパチ叩くコードを書いていて、「こんなのもうとっくに誰かが書いてんじゃないかなー」と思ってぐぐってみたらなんかすごいのが出てきた。


geocoder.png

Ruby Geocoder

住所と緯度経度の相互変換はもちろん、距離や範囲の扱い、Google以外のAPIへの対応、キャッシュ処理など、「実装しようかなー。でもめんどくさいよね」とか思って先送りしていたような機能がほとんど全部実装されている。

住所の取得

require 'geocoder'
# 日本語ロケールに設定
Geocoder.configure( :language  => :ja,   :units => :km )
Geocoder.address("東京タワー")
# => "日本, 〒105-0011 東京都港区芝公園4丁目4−2−8 東京タワー"

もちろん、住所しか取れないなんて馬鹿なことはなくて、GoogleMapsAPIの結果をそのままもらうこともできます

Geocoder.search("東京タワー")
# => [#
[{"long_name"=>"東京タワー",
"short_name"=>"東京タワー",
"types"=>["point_of_interest", "establishment"]},
:中略
{"long_name"=>"芝公園",
"short_name"=>"芝公園",
"types"=>["sublocality_level_1", "sublocality", "political"]},
{"long_name"=>"港区",
"short_name"=>"港区",
"types"=>["locality", "political"]},
{"long_name"=>"東京都",
"short_name"=>"東京都",
"types"=>["administrative_area_level_1", "political"]},
{"long_name"=>"日本",
"short_name"=>"JP",
"types"=>["country", "political"]},
{"long_name"=>"105-0011",
"short_name"=>"105-0011",
"types"=>["postal_code"]}],
"formatted_address"=>"日本, 〒105-0011 東京都港区芝公園4丁目4−2−8 東京タワー",
"geometry"=>
{"location"=>{"lat"=>35.6585805, "lng"=>139.7454329},
"location_type"=>"APPROXIMATE",
"viewport"=>
{"northeast"=>{"lat"=>35.6676459, "lng"=>139.7614403},
"southwest"=>{"lat"=>35.6495141, "lng"=>139.7294255}}},
"types"=>["point_of_interest", "establishment"]}>]

緯度経度から住所へ(リバースジオコーディング)

Geocoder.address("39.70150773,141.13672256")
# => "日本, 〒020-0034 岩手県盛岡市盛岡駅前通 県道2号線"
Geocoder.search("39.70150773,141.13672256")
[#"日本, 〒020-0034 岩手県盛岡市盛岡駅前通 県道2号線",
:(中略)
"types"=>["route"]}>,
#"日本, 盛岡駅前(バス)(岩手)",
:(中略)
"types"=>["bus_station", "transit_station", "establishment"]}>,
#"〒020-0034, 日本",
:(中略)
"types"=>["postal_code"]}>,
#"日本, 盛岡駅(岩手)",
:(中略)
"types"=>["train_station", "transit_station", "establishment"]}>,
:(以下略)

IPアドレスの場所を取得

Geocoder.search("210.194.198.60")
[#"210.194.198.60",
"country_code"=>"JP",
"country_name"=>"Japan",
"region_code"=>"19",
"region_name"=>"Kanagawa",
"city"=>"Yokohama",
"zipcode"=>"",
"latitude"=>35.4478,
"longitude"=>139.6425,
"metro_code"=>"",
"areacode"=>""}>]
Geocoder.address("106.187.50.98")
# => "Japan" # IPアドレスによってはこうなってしまう(笑)

距離や角度の演算

t1 = Geocoder.search("東京タワー")[0].geometry['location'].values.join(',')
t2 = Geocoder.search("東京スカイツリー")[0].geometry['location'].values.join(',')
Geocoder::Calculations.distance_between(t1,t2)
# => 8.20771162081327 # km
Geocoder::Calculations.bearing_between(t1,t2)
# => 51.834180520275936 # degree
Geocoder::Calculations.compass_point Geocoder::Calculations.bearing_between(t1,t2)
=> "NE"
# railsと組み合わせるとobj.distance_to みたいなこともできるらしい。

Bing APIを使ってみる

# APIキーが必要。http://www.bingmapsportal.com
Geocoder.configure( :lookup => :bing, :api_key=>'*****' )
Geocoder.search("東京タワー")
# => [#"Location:http://schemas.microsoft.com/search/local/ws/rest/v1", "bbox"=>[35.62964091461487, 139.69803606567407, 35.687581680599976, 139.79308331909155], "name"=>"Tokyo Tower, Japan", "point"=>{"type"=>"Point", "coordinates"=>[35.65861129760742, 139.7455596923828]}, "address"=>{"countryRegion"=>"Japan", "formattedAddress"=>"Tokyo Tower, Japan", "landmark"=>"Tokyo Tower"}, "confidence"=>"High", "entityType"=>"LandmarkBuilding", "geocodePoints"=>[{"type"=>"Point", "coordinates"=>[35.65861129760742, 139.7455596923828], "calculationMethod"=>"Rooftop", "usageTypes"=>["Display"]}], "matchCodes"=>["Good"]}, @cache_hit=nil>]
Geocoder.address("東京タワー")
# => "Tokyo Tower, Japan"

ほかにも、API呼び出しをキャッシュさせたり、Railsと組み合わせることで Hotel.near(“Vancouver, Canada”) みたいなこともできるようになるらしいです。

もっと早く探しておけばよかったなー。

[本] 実践!交渉学

「フットインザドア」とかの交渉じゃなくて、交渉がどういうものでどういう力学が働くのかといった解説をした本。無意識に実践しているような内容も多かったのですけど、体系的にまとまっているので思考を整理することができました。

読書メモ。

  • 「窓を開けたい」人と「窓を閉めたい」人が交渉する場合、そのままだと要求を突きつけあうだけで妥結の余地がない。けれども、「なぜ窓を開けたい/閉めたいのか」情報交換して「風が吹き付けるから寒い」「空気を入れ替えたい」という利害を明らかにすることで、両者の利害を満たす結論を出すことができる(「ここじゃなくて、むこうの窓を開けましょう!」)
  • 『BATNA』(不調時代替案)。交渉が破談になったとき取れる対応のこと。
    • たとえば中古ノートパソコンを友達から買う場合、最悪ヤフオクで買ってもいいのであれば、ヤフオクで買う場合の想定値段がBATNAになる。
    • 自分のBATNAを明かしてはならない。「ヤフオクで10万円だったからそれより安く」と言ってしまうと、たぶん9万8千円くらいで買うことになる。相手は5万円でもいいかな、と思っていたかもしれないのに!
    • 相手のBATNAを知っていれば有利に交渉ができる。友達が捨てるしかないと思っているのなら、一万円と申し出てもお得だと思ってくれるかもしれない。
  • 『ZOPA』(交渉可能領域)
    • お互いのBATNAを並べて譲りうる余地のこと。最低1万円で売りたいA君と、ヤフオクで買えば10万円で買えるB君の間では、1万円から10万円までの間がZOPAになる。
    • Aくんが20万円で売りたい場合は、ZOPAが消滅するので交渉の余地がなくなる
    • 「パレート最適」ZOPAの中で、お互いの利益が最大化する解の事(略)
  • この本の前半分がこんな感じの、1対1の交渉理論について書かれている。後半は、より複雑な、複数の人が絡んだ場合の交渉学。ビジネス上の交渉で使うことは少なそうだけれど、世の中なんでこんなことになっているかを理解するうえで面白かった。
    • WIN-WINってけっこう不公平だよね、という話
    • ステークホルダーを洗い出す重要性について
    • 合意形成について
    • マスコミの役割と、マスコミによっておきやすい問題について
    • 専門家が必要な交渉で何が起こるか、それに対する対処など

[sublime text2]選択中の文字列で検索する

Preferences.sublime-settings.png

Sublime Text 2で以前から気になっていた問題。command+Fで検索パネルが開くのだけれど、カーソルで文字列を選択した状態でcommand+Fしたら選択された単語を検索してほしい。

秀丸エディタがそういう挙動だったので、これがないととっても不便。
機能としてはslurp_find_stringというコマンドがあるみたいなので、いっそプラグインを書くか、と思って色々調べていたのだけれど、設定一発でできることが判明しました。command+,で設定画面を開いたら
“find_selected_text”: true

を追加するだけ。

関連する不満だった、検索パネルを開いた時カーソルが検索後の後ろにいる(そのまま文字を入力したら以前の検索語の後ろに入力される!)という謎仕様も、この設定で改善されます。検索パネルを開いたら検索語が選択された状態で開くから、command+aで単語を上書きしなくても、そのまま検索語を入力することが出来ます。

Titanium SDKとiOS/Android SDKの関係

iOS7に対応したTitanium SDK 3.1.3が正式リリースになりました。
さっそく使おうかと思ったのですが、ひとつ気になることが。TitaniumSDKのバージョンをあげるということは、古いiPhoneやAndroid端末のサポートを切り捨てることになったりしないかな?

結論から言うと、3.1.3にあげても市場にある大半の機種で動作するようになっているので、あんまり気にしなくてよかったわけですが、せっかく調べたので一応まとめておきます。

特定のバージョンのTitanium SDKがサポートするiOS/Android SDKの範囲はこの辺に載っています。

まとめるとこんな感じ

Titanium SDK Version Min iOS/SDK Version Max iOS/SDK Version
3.2.0 6.0.x 7.0.x
3.1.3 5.0.x 7.0.x
3.1.1 – 3.1.2 5.0.x 6.1.x
3.1.0 4.3.x 6.1.x
2.1.3 – 3.0.x 4.0.x 6.1.x
2.1.0 – 2.1.2 4.0.x 5.1.x
2.0.x 4.0.x 5.1.x
1.8.x 4.0.x 5.0.x
1.7.1+ 3.1.2 5.0.x
1.7.0 3.1.2 4.3.x
1.6.1+ 3.1.0 4.3
1.6.0 3.1.0 4.2
Titanium
SDK Version
Min
Android/SDK Version
Max
Android/SDK Version
3.1.2
– latest
2.3.3 (API
10)
4.3.x (API
18)
3.1.1 2.3.3 (API 10) 4.2.x (API 17)
3.1.0 2.2 (API 8) 4.2.x (API 17)
2.1.2 – 3.0.2 2.2 (API 8) 4.1.x (API 16)
2.0 – 2.1.1 2.2 (API 8) 4.0.x (API 15)
1.8.x 2.2 (API 8) 3.x.x (API 11)
1.7.x 2.1 (API 7) 3.x.x (API 11)

Android

Dashboards | Android Developers(公式データ)

Version Codename API Distribution
2.2 Froyo 8 2.4%
2.3.3 –

2.3.7
Gingerbread 10 30.7%
3.2 Honeycomb 13 0.1%
4.0.3 –

4.0.4
Ice Cream Sandwich 15 21.7%
4.1.x Jelly Bean 16 36.6%
4.2.x 17 8.5%

2.2は切っていいような気がします。Xperia(SO-01B)とau IS03を切り捨てて、Galaxy S(SC-02B)がぎりぎり生き残るくらいの目安です。
Android 2.3.3以降をサポートすることにすれば、TitaniumSDKは最新の3.1.3でも大丈夫なことになりますね。

iOS

ios_versions.png


iOSとAndroidのバージョン分布、各OS内の割合はiOS6が93%、Android最新バージョン(Jelly Bean)は33%: 「最高のタブレット」を求めて!
(公式データのコピー)

iOS6が93%、iOS5が6%。それ以前は切ってよさそう。iPhone3GSですらiOS6対応ですから、iPhone3Gを切るくらいは十分アリでしょう。
3.1.3(=iOS7対応)でiOS 5.0-7.0サポートなので、これを使って問題なさそうです。

以上、Titanium SDK 3.1.3を使っても古い機種を切る心配はしなくてよさそう、という調査結果でした。
3.1.3、安定して動いてくれるといいのですけど・・・