2012年4月20日金曜日

Objective-Cにおけるselfとsuperの扱い

iOSの勉強してるのでいくつかメモ。

語りつくされた話なんだろうけど、Objective-Cとは、"C言語上にSmalltalkライクなマクロを実装し、オブジェクト指向を扱えるようにした" とでも表現すべき奇怪な言語である。

ゆえに言語規則として書かれていることでも、実際には言語としてサポートされているわけではなく、構造体やポインタなどC言語の機能で再現されているだけだったりする。ほとんど制約らしい制約がないため、言語規則をぶっちぎった最適化やハックが可能なフリーダムさが魅力。

一見するとオブジェクト指向的なキーワードである、自身を意味するselfや、スーパクラスを意味するsuperの奇妙な振る舞いもそのことが起因している。

self

selfの正体とは、メッセージによってメソッドが呼び出された際に、ランタイムによってレシーバに渡される隠し引数。つまりObjective-Cにおけるselfとは、ただのローカル変数に過ぎないのである。

そして注意すべきこととして、Objective-Cではselfという同じ変数名でありながら、文脈によって異なる中身を返す

インスタンスメソッドではインスタンス自身が渡され、クラスメソッドではクラスオブジェクトが渡される。

クラスオブジェクトとはプログラム実行時にクラスごとに一つだけ生成される、クラスの雛形となるオブジェクトのこと。

javaならばクラスメソッドに相当するstaticメソッドでは、selfに相当するthisキーワードは使えない。しかしOjbective-Cをjavaに例えると、staticメソッドではthisがHogeClass.classのような働きをするのである。

super

ではsuperとは何かといえば、ランタイムがメソッド検索する場所を切り替えるフラグの役割を果たしているのである。

通常のメソッドを呼び出しでは、受け取ったインスタンスのクラスでメッセージを処理する。superはそのメソッドを定義したクラスの親クラスのメソッドを検索してメッセージを処理する。

ゆえに単なるローカル変数のselfでは値を代入したり、selfを返したりできるが、superには代入も返却もできず、できることはメッセージを受け取ることである。「定義されたスーパークラスでメッセージを処理する」という特殊なレシーバとみなすことができる。

selfとsuperのズレ

ゆえにselfとsuperが指すものはクラス階層によってズレが生じる。

SuperHoge、SuperHogeを継承したChildHoge、ChildHogeを継承したGrandchildHogeという3つのクラスを考える。

SuperHogeはhogeメソッドを実装し、これをChildHoge、CrandchildHogeがそれぞれオーバライドして独自の処理を行っているとする。

ここでChildHogeがselfに対してhogeメソッドを呼び出すcallSelfと、superに対してhogeメソッドを呼び出すcallSuperメソッドを実装した時、各々のメソッドが呼び出すhogeメソッドの処理はどのクラスのものになるか?

ChildHogeのときは直感通りになる。すなわち、callSelfではChildHogeがオーバーライドした処理を行い、callSuperではSuperHogeで実装した処理となる。

ではGrandchildHogeが、ChildHogeより継承したcallSelfとcallSuperメソッドを呼び出すとどうなるのか?

callSelfではGrandchildHogeで実装した処理が、callSuperではSuperHogeで実装した処理となるのだ。

重要なのは2点。

  • selfとはメソッドを呼び出したGrandchildHogeインスタンスが代入される変数に過ぎないから、callHogeが[self hoge];を実行したとき指すselfはGrandchildHogeである。
  • superはメソッド定義クラスの親クラスのメソッドを呼び出すスイッチャであるから、ChildHogeインスタンスでもGrandchildHogeインスタンスでも更にその子孫であっても、ChildHogeで定義された[super hoge];が実行するのはSuperHogeクラスの処理である。

言葉で書いていても伝わりにくいので、簡単だし実証コードでも書くべき。この違いはかなり重要だと思った。

2012年4月9日月曜日

標準ランチャー周りの役に立たない知識

ホームにショートカットをおきたいとかランチャに対して何かをしたいことはあると思うんだけど、アプリの機能ごとにショートカットを作るなど、説明自体は割りと転がってるんだけど、標準ランチャの内部にまで突っ込んだ話は見ないので。

まずアプリでホームにショートカットを生成したいとき。

//ショートカットを作りたいアクティビティのインテントを作る
Intent shortcutIntent = new Intent();
shortcutIntent.setClassName("com.hoge.hoge", "hogeActivity");

//ショートカットを登録するインテントを作る
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(intent.EXTRA_SHORTCUT_NAME, "ほげほげ");
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
setBroadcast(intent);

実際テストコードを書いてないけど、INSTALL_SHORTCUTのパーミッションを与えれば、これで生成して問題なく使えるはず。アイコンも設定できるけど、それは他の場所を参考にして下さい。

標準launcherはINSTALL_SHORTCUTのブロードキャストを受け取ると、そのインテントからuriを受け取り、アクションがなければACTION_VIEWを付与してショートカットを生成する。

しかしこの場合、アプリケーションを削除してもショートカットが残り続けるため手動で削除しなければならない。

なぜかというと、標準launcherはパッケージのアンインストールを感知する(正確にはcom.android.launcher.action.UNINSTALL_SHORTCUTのインテントを受け取るんだけど、これを誰が投げてるのかは分からない…)と、dbに格納されたショートカットとuriを比較して、同じだった場合にのみ削除を行うのだけれど。

このとき比較に、Intent#filterEqualsを使っているので、アクションとカテゴリも一致させないと同じuriだと認識されない

巷に転がっているショートカット生成コードは、classNameしか一致させていないので、パッケージ削除時にショートカットが削除対象から漏れてしまう。

//ショートカットを作りたいアクティビティのインテントを作る
Intent shortcutIntent = new Intent();
shortcutIntent.setClassName("com.hoge.hoge", "hogeActivity");
shortcutIntent.setAction(Intent.ACTION_MAIN);
shortcutIntent.addCategory(Intent.CATEGORY_LAUNCHER);

以上のようにアクションとカテゴリを追加すれば、アンインストール時に消えるショートカットになる。見ているのはclassNameとactionとcategoryだけなので、他にデータを付与したり、アイコンやショートカットの名前を変えても問題ない。

しかしこれでもまだ問題がある。ショートカット作成インテントを投げる度に無尽蔵にショートカットが作成されてしまう。どうすればいいのか?

標準launcherのコンテンツプロバイダ、content://com.android.launcher2.setteings/favoritesから、現在ホームに存在するショートカット一覧を取得して、もしすでに存在する場合にはショートカット生成インテントは飛ばさない、とやるのが一つの方法。

余談だけどREAD_SETTINGSパーミッションは必要とはいえ、protectionLevelはnormalなので、「他人のホーム画面に並べてるアプリ情報を抜いて趣味嗜好を観察する」とか、「自分のアプリがホームで一軍として利用されているか」とか、そういう使い道もできてしまう。

実はそんな面倒なことしなくても、duplicateをfalseにしておけば、ショートカットが重複して生成するのを抑制することができる。デフォルトの挙動はtrueなのが、不親切な感じ…。

//ショートカットを登録するインテントを作る
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(intent.EXTRA_SHORTCUT_NAME, "ほげほげ");
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
intent.putExtra("duplicate",false);
setBroadcast(intent);

2012年4月8日日曜日

フォーカスについて

XMLのrequestFocusでフォーカスを取得できるのは、最初のsetContentViewのみ。

一つのアクティビティで複数のビューをセットした場合、二度目にinflateしたXMLのrequestFocusには反応しない(…と思う。この辺りちゃんとコード読んで理解した方がよさそう)ので、コード上でフォーカス処理を書く必要がある。

2.0系OSで困るケースはほとんどないと思うけど、3.0系以降のフラグメントを使う際にこういう症状に遭遇した。

EditText周りのメモ

Editableインターフェース

AndroidではTextViewでの文字列の扱いにStringクラスやStringBuilderクラスではなく、Editableインターフェースを用いる。


Editableという名前から、不変なStringクラスではオーバーヘッドがあるため特別なインターフェースがあるのではないか?と想像するが、それならばStringBuilderやStringBuffer、CharSequenceではなぜダメなのか?という疑問が浮かぶ。(この辺javaをよく理解していないからこその疑問かもしれないけれど)
結論から言えば、Editableは文字装飾と入力フィルタを備えた特殊な文字列クラスとして用意されている。


文字列装飾はより正確に言えばEditableインターフェースが実装しているSpannableインターフェースにおいて、装飾追加と除去を行うメソッドが定義されている。装飾の実体は抽象クラスCharacterStyleのサブクラスで実装されている。


InputFilterインターフェースのfilterメソッドに記述されたルールに従い、Editableに入力された文字列にフィルタリングを行う。XMLレイアウトエディタでTextViewを配置した際に、InputTypeやmaxlengthで入力できる文字列を制限した際には、内部では相当するInputFilterインターフェースがセットされている。
コードで記述するとより柔軟で強力なフィルタリングルールを作れる。


IMEとの連携


IME(Input Method Editor)とはキーボード入力を補助するソフトウェアのこと。ハードウェアキーボードを用いる一般的なPCでは日本語変換機能のことを差すが、スマートフォンでは仮想キーボードも立派に入力補助ソフト一部。
Androidにおけるソフトウェアキーボードは、サービスとして実装されている。タッチ入力を受け取り、それを解釈したらキーイベントに変換して投げている。


EditableインターフェースはIMEと連携して、IMEのアクションボタンを変化させられる。例えば入力欄が複数ある際に他の入力欄にフォーカスを移す「次へ」、入力欄が検索バーならば「検索する」ボタンを表示させる、逆にメールなど誤送信を防ぐために入力を押しても即座にアクションを起こさないようにするなど。
XMLのIMEOptionsでアクションを定義して、onEditorActionリスナをセットして、その中にイベントハンドル内容を書けば良い。

2012年4月6日金曜日

hello blogger

public static void main(){
   System.out.println("hello, syntaxhighlighter");
}


使うのか知らないけど一応入れてみる。