2014年6月6日金曜日

Swift の enum型を for-in でイテレーションする方法

例えばJavaのEnum型などはそのまま以下のようにイテレーションすることが可能なのですが、

なぜかSwiftのenum型はそのままではイテレーションすることができません。対策としてGeneratorという仕組みが標準ライブラリに用意されてますので、それを使ってenumをイテレーションできるようにします。

具体的には、Generatorを継承したクラスを作成して next() -> Element? を実装してください。ElementはAnyObjectのtypealiasなので実際には好きな型を返していただければOKです。あとはSequenceOf<T>型でGeneratorをラップしてあげればOKです。next()メソッドがnilを返すまでSequenceOf<T>はイテレーションを続けてくれます。

以下にサンプルコードを示します。

Generator内部でyieldが使えれば便利なんですが、おそらくyield構文は無さそうです。Enum型を一覧したい場合以外にもGeneratorは便利に使えますのでぜひお試しください。

Swift を使ってみてがっかりした点まとめ



数日間iOS8/Xcode6/Swiftな環境で色々試してみて、Swiftを使っていて思ったよりがっかりした点が多かったのでちょっとまとめてみようと思います。

動的な処理がSwiftだけでは一切できない

[NSObject performSelector:]の類と、NSInvocationがSwiftからは一切呼び出せません。使おうとすると怒られます。objc/runtime.hは試していませんが、同様に直接Swift経由では呼び出せず間にObjective-Cをかます必要があるのではないかと思われます。

@optionalなprotocolが限定的にしか使用できない

具体的には@objc属性を付けないと使えません。しかしながらこのような後方互換性のためだけに存在する属性をいつまでもAppleがサポートするかは疑問が残るというのと、もう一つ以下の様な問題があります。

@objc属性のついたSwiftの型はただのObjective-Cクラスになる


こういう問題があるのであまり使いたいとは思えません。ちなみになのですがCocoaのクラスはほぼすべて@objc属性が付いているため、それを継承して使うことになるアプリでは事実上Swiftの本来の能力を出せないのではないかと思っていますが、実際のところはわかっていません・・・

メモリ管理が相変わらず必要

Swiftのメモリ管理はGCではなくARCでありただの参照カウント方式にすぎないため、Swiftでも循環参照が発生しないようにプログラマが明示的に参照の種類を指定しなければなりません。その上Objective-Cでも存在したstrong, weakに加えunownedという新しい種類のメモリ管理が追加されています。これはweakは参照が消滅するとnilにするという挙動であるためOptional型を使わなければならないのに対し、unownedは参照が消滅してもnilにならない代わりに通常の型がそのまま使えるというもののようです。

closureでselfをキャプチャするときの循環参照対策が相変わらず必要

いちばんがっかりしたのがこれです。Swiftはdelegateよりもclosureを使ったcallbackのほうが言語構造上向いているためclosureを大量に使うことになると思うのですが、このときselfがclosureを強参照し、closureがselfをキャプチャするようなコードを書いてしまうと、循環参照になるためメモリが開放されなくなるという問題がObjective-Cから引き続き発生します。対策としてclosure capture listと呼ばれる新たな構文が追加されています。closureの先頭、引数宣言の前に[unowned self]のような構文を追加することで、selfをunownedとしてキャプチャすることができます。

以下に使用前・使用後の例を示します。

Objective-CのweakSelfよりはマシに思えますが、とはいえこの辺りはコンパイラが自動的に対応してほしいところです(´・_・`)

2014年5月26日月曜日

Android で 画面の回転や状態の復元まで考えた Fragment の使い方のガイドライン(自分用メモ)

Fragment を使った画面を作る際に、どのように作ればうまい具合に画面の回転や状態の復元を扱えるかという自分用のメモです。

最初にまとめ

  • 基本方針として可能な限りすべての管理を当該ActivityのFragmentManagerに任せると楽
  • ActivityのフィールドとしてFragmentを保持するのはバッドノウハウな気がする
  • onCreateとonDestroyが呼び出されたからといってインスタンスが生成破棄されているとは限らない、これらはFragmentManagerのタイミング次第
最終的に実装したコードは以下のような感じになりました。

今回の発端

ActionBarのタブに2つのFragmentを格納し、片方はListView, もう片方はGoogle MapsのMapViewを突っ込むようなUIを作っていたのですが、Androidは素人なもので普通に作っていると画面回転とタブの切替時にうまいこと状態を復元するのがなかなか手こずってしまいました。というわけで良いプラクティスを考えてみることにしました。

Activity と Fragmentのライフサイクルを復習

Activityは画面回転時に一度破棄されてしまいます。このような場合はActivityのFragmentManagerが現在管理しているFragment(バックスタックに入っているものが含まれるかどうかは未検証)については、Activity破壊時にFragmentManager経由で自動的にonSaveInstanceが呼び出され、Activity復旧時に自動的にonCreateとonCreateView経由で復元が試みられます。

これとは別に、Activityは破棄されないがFragmentは破棄されるケース、例えばActionBarのタブを切り替えたりNavigationDrawerを選択するなどして同一のActivity上で別の画面Fragmentに遷移する場合もあります。

上記いずれの場合も、可能な限りテキストビューの入力内容やリストのスクロール位置、地図のカメラ位置などを保持することをユーザーから期待されるため、状態の復元が必要になります。画面を回したりタブを切り替えたらスクロール位置が先頭に戻ったらユーザーはイライラするでしょう。

状態の保存はBundleとonSaveInstanceStateを使い、復元はonCreateとonCreateViewを使うのが楽です。

解決策

画面回転などActivity自体の破棄と再生成が自動的に行われるケースであればFragmentManager管理下にあるFragmentについて自動的に再生成が試みられるため大して難しくはないと思います。タブを切り替えたりするケースについては、以下のいずれかが良さそうな気がしています。
  • 解決策1: すべてのFragmentをFragmentManagerにattachされた状態のままにし、タブが切り替えられたら見せないFragmentはhideする
  • 解決策2: 必要に応じてFragmentManagerにattach/detachを行い、そのかわり自分でBundleを作りonSaveInstanceStateを呼び出す
1のメリットはタブ切り替え時に復元がそもそも発生しないため管理が簡単です。確実に動作しますし、再生成も必要ないためパフォーマンスも良いです。デメリットはFragmentおよびFragmentが抱えるView構造をすべて保持し続けるためメモリを大量に消費します。

今回採用した解決策2のメリットはタブ切り替え時にFragmentのView構造をすべて捨てるためメモリが効率的です。MapViewはどうしてもメモリを大量に使うためいくらhide状態とはいえあまり他のタブの後ろにおいておきたくはなかったのでこうしました。デメリットはやはり複雑になります。今回はFragmentのインスタンスフィールドとして一時的にBundleを保持していますが、これは正直なぜFragmentがタブから外れてDetachされてDestroyされてるのにメモリ上に残ってるのかわかりづらい変な挙動になるので、Activity側かまたは何らかのマネージャクラスに任せてしまうべきではないかと思います。・・・ってそれがFragmentManagerなんですけど。もっとうまいやり方で出来そうな気がするんですが・・・


2014年4月30日水曜日

Android で Dagger DI を使いやすくするライブラリを書きました

Daggerというsquare社がオープンソースで提供しているAndroid向けDI (Dependency Injection)フレームワークがあります。


これを試しに自分のAndroidアプリで使ってみようと思い立ったのですが、幾つか問題が発生しました。

  • DI自体の概念が難しい
  • そもそもドキュメントを読んでもDaggerの使い方がよくわからない、公式のサンプルを真似してみても正直いまいちわからない
  • AndroidでDIを行うとなるとandroid.content.Contextの注入が必須になるのだが、Contextは動的なインスタンスであるためDIでの取り扱いが難しい

そこで四苦八苦しながら動くようになったものをライブラリとして公開し、少しでも簡単にDIのメリットだけを享受できればと思いまして DaggeredAndroid なるものを作ってみました。

使い方とかはREADMEを見てください。全部英語ですがすみません(´・_・`)

AndroidでDIを使う際のメリットは主に以下のとおりです。

  • オブジェクトをメンバに接続するだけのコードを無くせるので、コード量が減る。
  • シングルトンの取り扱いが楽になる。
  • Contextの取り扱いが楽になる。
  • Moduleを差し替えればインスタンスが安全に差し替わるので、テスト環境を作ったり、本番と開発環境を分離したりなど、環境の差し替えが楽になる。テスト時のみModuleを上書きすることもできる。

2014年4月20日日曜日

Android の TextView.setText() が遅い場合の原因と対処法

AndroidでTextViewを使っている時に、setText()に数百行単位のテキストを渡すとメインスレッドが1秒弱完全に固まってしまうという現象に見舞われてしまいました。昔の2.3端末ではともかく、手元の最新鋭機Nexus 5 (Android 4.4)でこんなに遅いのでは話になりません。しっかりと原因を調査し対処法を考えることにしました。

まずググってみると出るわ出るわ同じ問題。やはりみんな同じ場所で躓いているようです。
しかしながらいまいち具体的な原因がググっても見つかりません。そこでtraceviewを取ってみました。


すると原因が一発でわかりました。android.graphics.Paint.getTextRunAdvances()です。
Nexus 5では高速化のためJNI経由でネイティブ実装が呼び出されているようですが、それでもまだ間に合わないぐらい遅いようです。それもそのはず、このメソッドは与えられた文字の幅を計算するメソッドです。すなわち数百行のテキストのサイズを計算するため時間がかかっているようです。iOSで例えるならCore TextのCTGryphを計算するようなもの、UILabelのsizeThatFitsを呼び出すようなもので、非常に時間がかかってしまいます。

そこで対処法として、setText()でテキスト全体をセットし直すのではなく、TextViewが裏で保持しているテキストの一部だけを書き換えたり追記したりすることで一度に計算されるテキストのサイズの量を減らして高速化する事を考えました。iOSの場合はUITextViewにはsetText相当のプロパティしか用意されていないので、そのようなことをするのはdelegateを経由してみたりUIKeyInputプロトコルを自前で用意したりなどと困難がつきまとうのですが、Androidの場合は最初からTextViewの裏で保持しているテキストを自在に書きなおすための仕組みが用意されています。

そのためにはまずTextViewの裏で保持されているテキストを「編集モード」にしなければなりません。XMLでandroid:bufferTypeをeditableに指定するか、またはsetText()の第二引数にTextView.BufferType.EDITABLEを指定すると、テキストが編集モードで保持されるようになります。

そうするとgetEditableText()でTextViewが裏側で保持しているテキストが編集可能な状態で取得できます。あとはこのEditableオブジェクトに対して好きなように加工を行うだけです。単にテキストを追加するだけならTextView.append()を実行しても同じ結果が得られます。

こうすると数百行程度であればそれほど遅くなくテキストの追加ができるようになりました。しかしながら1000行を超えてくるとこれでも速度が足りなくなるので、自前でTextViewをサブクラス化して作っていくか、またはListViewにして一度に表示するテキスト量を減らすのが良いと思います。


2014年4月1日火曜日

Objective-🍣


全く新しい、真にユニバーサルな言語へ。


皆さんiOS開発の際にお世話になっているObjective-Cですが、一部の開発者の方々から以下の様な否定的な意見をいただくことがあります。

  • とっつきにくい
  • 文法がキモい
  • @や[]がキモい
  • シグネチャが無駄に長い
  • Apple製品でしか使えない

確かにObjective-Cは習熟すればこれらの欠点を補って余りある素晴らしい言語ですが、これからの更なるモバイルアプリの世界の拡充のためにはより一層多くの開発者に愛される言語になる必要があると私は考えました。

そこでこの度ご紹介するのがObjective-Cをさらに使いやすく、さらに親しまれるように、全く新しく一から作りなおした新言語

Objective-🍣

です!

Objective-🍣とは

以下の様な特徴を持つ言語です!

  • 驚異的に短く、真にユニバーサルな、洗練された文法を持ちます。これまでのプログラミング言語は基本的に英語による記述を強いるものでした。Objective-🍣は真に全人類にとってユニバーサルな絵文字をサポートすることで、この問題をすべて解決しました。Objective-🍣の親しみやすい文法にはすべてのプログラマがシンパシーを感じることができます。
  • Objective-Cと100%のランタイム互換性を持ちます。新しいiOSが登場してもその瞬間からあなたはObjective-🍣の圧倒的なパワーを手にすることができます。

さっそくObjective-🍣で書かれたサンプルコードを見てみましょう!拙作確率計算機のコードをObjective-🍣に書き換えてみました!

before

after

なんというこれまでにない全く新しいソースコード!あのキモかった@や[]、長ったらしいメソッド名がその姿を消しています!そのあまりの美しさには全プログラマが歓喜の涙を流すこと間違いありません!

Objective-🍣の導入方法

こちらのobjsushi.hをあなたのプロジェクト上でincludeするだけで使用できます!簡単ですね!


Objective-🍣を使ってみる

それでは早速ビルドしてみましょう!

あれ

えっちょ

\(^o^)/

ちなみに日本語セレクタ自体はXcode 5以降で普通に使えますよ。

お詫び

こちらの記事にはiOSならびに最新のMacでのみご覧になれる文字(具体的には🍣)を多数含んでおりますことをお詫び申し上げます(´・_・`)

2014年3月20日木曜日

iOSアプリ「確率計算機」をリリースしました


iOSアプリ「確率計算機 (YourLuck)」をリリースしました。
ガチャのドロップ率と試行回数からドロップ期待値を計算するアプリです。
無料です。
https://itunes.apple.com/jp/app/que-lu-ji-suan-ji-gachano/id838156105?mt=8 (JP store)
https://itunes.apple.com/app/id838156105?mt=8 (Universal)

機能

シンプルイズベストなので、以下の2つだけです。

  • 確率計算・・・何回ガチャを回したら、最低でも1個はドロップするか、確率を計算します。
  • 期待値計算(v1.1.0以降)・・・ガチャを指定された回数だけ回すと、一体何個ぐらいはドロップするのか、ドロップする数を計算します。


使い方

特別なことはなにもないのですが、以下のように操作します。



期待値計算モード(v1.1.0以降)

画面左下のこのアイコンを押すと、期待値計算モードに切り替わります。通常の確率計算モードでは「指定された試行回数分だけガチャを回したら最低でも1個はドロップする確率」を表示しますが、期待値計算モードでは「指定された試行回数分だけガチャを回したらだいたい何個ぐらいドロップするか」を瞬時に計算する事ができます。同じモンスターを複数体狙っている時などに便利です。


FAQ

Q: 用語が難しくてよくわからんのだが
A: めっちゃバッサリ言うと以下のとおりです。
  • ドロップ率・・・ガチャのドロップ率です。そのまんまですね。
  • 試行回数・・・何回ガチャを回すかです。
  • 中央にでっかく出る%・・・指定したドロップ率で、指定した回数だけガチャを回したら、100人いるうちここに出ている数字の人が最低1回は指定したドロップ率の何かが出る、という意味です。30%だったら100人のうち30人が最低1回出ます。運がいい人であれば2回以上出るかもしれません。

Q: 絶対確実って表示されてるのに表示されてる試行回数分だけ回しても出なかった!詐欺だ!!!
A: 申し訳ありません(´・_・`) これには幾つか理由があります。
  • 確率は確率なので、例えば98.7%で絶対確実と表示されても1.3%の人、1000人に13人の人は外れてしまいます。これは年末ジャンボ宝くじを1枚だけ買ったら5等3000円が当たった、ぐらいの確率です。すごいのかすごくないのかイマイチわからんですね。
  • 試行回数が多いと誤差が出る可能性があります。具体的には試行回数100回以上はより高速な計算を使っているのですが、そのため精度1~2%程度ですが落ちている可能性があります。

Q: 期待値モードで「最低でも」とか「ほとんどの人は」とか出るけどあれ具体的には何%なの?
A: 以下のとおりです。
  • 最低でも・・・100人中98人がこの数字以上の個数を獲得できます。
  • ほとんどの人は・・・100人中85人がこの数字以上の個数を獲得できます。
  • 半数の人は・・・100人中50人がこの数字以上の個数を獲得できます。

Q: ガチャの確率ってどこで調べればええの?
A: 以下の様な方法をオススメします。
  • ゲーム内に表示されている場合があります。最近のゲームでは特に確率を表示しているものが増えているので、ガチャの画面で注意して探してみてください。
  • グーグルとかヤフーで「ゲーム名 ガチャ 確率」と入力して検索する。
  • 知ってそうな人に聞く。ただしゲームを作ってる中の人とか関係者にTwitterで聞いても絶対に教えてくれないのでそういう人以外に聞いてください。

サポートとか

もし何かございましたら、以下の私の連絡先にまでご連絡ください。
mail: akisutesamaあっとまーくgmail.com
twitter: @akisutesama