Xcode4でhello world
iPhoneアプリを作ってみよう(Xcode4編)第1回:じゃんけんゲーム - もとまか日記Z
上記記事を参考にしつつ、helloworldに挑戦します。
画面のボタンを押したら画面上のラベルをhello worldに書き換えるようなアプリにしてみます。
かなりあいまいな理解の部分もあるので、鵜呑みにしないよう気をつけてください。
xcode4は有料
xcode4からは有料なんだそうです。iTunesStoreで\600で買いました。4.5GBあり、DLには結構時間がかかりました。
プロダクト作成
テンプレートView-Based Applicationで作ってみます。
Product Name:HelloWorld
Company Identifer:com.example
Company IdentiferはJavaのパッケージみたいなものなんでしょうか?とりあえず調べるのは後回しにします。
レイアウトはxibファイルで指定
画面上のコンポーネントは、xibファイルに定義するようです。
ラベルやボタンをドラッグアンドドロップで配置してみます。
xibファイルの中身は?
xibファイルをOpen As Source Codeで開いてみると、普通のxmlのように見えます。
が、ひとつのコンポーネントに対応してる感じに見えます。IBUIViewという部分があり、この上にコンポーネントを配置していくのがView-Basedってことでしょうか。
ボタンを押したときの処理を記述する
HelloWorldViewController.xmlに定義したボタンを押したとき(タップ)の処理は、
HelloWorldViewController.hに定義するみたいです。
#import <UIKit/UIKit.h> @interface HelloWorldViewController : UIViewController { } // アクションメソッド -(IBAction) onTouchUpInside:(id)sender; @end
onTouchUpInsideというメソッドを定義しました。
これを、xibエディタでボタンのTouch up Insideイベントに設定します。ドラッグアンドドロップで設定。
xibファイルのオーナーがHelloWorldViewControllerなので、*.hで定義したアクションメソッドをxibファイルから見れるようになってます。
interfaceを実装
hファイルで宣言したアクションメソッドはmファイルで実装します。とりあえず空のメソッドを記述しmす。
#import "HelloWorldViewController.h" @implementation HelloWorldViewController // アクションメソッドの実装 - (IBAction) onTouchUpInside:(id)sender { // ここにボタンが押されたときの処理を書く } - (void)dealloc { [super dealloc]; } // 後略 @end
画面上のコンポーネントをコードから参照する
ボタンが押されたときにラベルを書き換えるには、ラベルへの参照を取得する必要があります。そのためには、ViewControllerがラベルへの参照を保持するフィールドを定義します。
まず、hファイルにラベルの参照を保持するフィールドを定義します。
#import <UIKit/UIKit.h> @interface HelloWorldViewController : UIViewController { IBOutlet UILabel *label; } // アクションメソッド -(IBAction) onTouchUpInside:(id)sender; @end
ただフィールドを定義するだけではだめで、IBOutletという修飾子が必要です。これをつけて定義したフィールドは、xibファイルから参照できるので、アクションメソッドのようにD&Dで設定します。
設定したら、mファイル内に実装したアクションメソッド内で、ラベルを書き換えるコードを記述します。
// アクションメソッドの実装 - (IBAction) onTouchUpInside:(id)sender { label.text = @"hello world!"; }
これでプロダクトをRunさせ、iOSシミュレータ上に表示されたボタンをクリックすると、ラベルが書き変わることを確認できました。
注意
最初にも書きましたが、かなり曖昧な理解で書いている部分もあるので、鵜呑みは禁物です。
HandlerThreadとHandlerとLooperの関係
IntentServiceを使って非同期処理を行う - Tech Booster
上記で紹介されているIntentServiceについて、どういうものなのか調べようと思ったら、HandlerThreadというものが内部的に使われていました。
気になる名前のクラスなので、こちらを先に押さえることにします。
HandlerThreadを使うと何が出来るのか
まず調べた結果を報告します。HandlerThreadは、Handler経由でメッセージをsendできる拡張スレッド、のようです。下記は調べたメモです。
IntentServiceのソースコード
HandlerThreadを調べる発端となったIntentServiceは、Serviceのサブクラスです。ソースを見てみると、onCreate時にHandlerThreadを生成して、スタートさせています。
IntentService.javaのonCreate
@Override public void onCreate() { // TODO: It would be nice to have an option to hold a partial wakelock // during processing, and to have a static startService(Context, Intent) // method that would launch the service & hand off a wakelock. super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); }
さて、このHandlerThreadとは何者でしょうか。
HandlerThreadは内部にlooperを持つスレッド
HandlerThreadは、UIスレッドのようなlooperを内部に持つ拡張スレッドのようです。
Handlerは、引数なしで生成すると生成時のスレッドのlooperがsend先になります。
Handler.javaの引数なしコンストラクタ
public Handler() { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = null; }
HandlerをUIスレッド以外からnewすると「Can't create handler inside thread that has not called Looper.prepare()」とよく怒られていたんですが、やっと合点がいきました。引数なしで生成されたHandlerは問答無用でUIスレッドにメッセージをsendするわけではなくて、newされたスレッドのlooperに対してメッセージをsendしていたんですね。
IntentService#onCreateでやっているように、new Handler(Looper looper)のコンストラクタを使うと、引数で渡されたlooperに対してメッセージをsendできるようです。
HandlerThreadを試してみる
上記を踏まえて、HandlerThreadの動作を試してみます。
まず、UIスレッドにHandler経由でメッセージをsendするコードです。
public class SampleHandlerThreadActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btn1 = (Button) findViewById(R.id.button1); btn1.setOnClickListener(new OnClickListener() { public void onClick(View v) { Handler mainHandler = new Handler(); mainHandler.post(new Runnable() { public void run() { Log.v("hoge", "thread name:" + Thread.currentThread().getName()); } }); } }); } }
実行結果
04-10 15:41:58.006: VERBOSE/hoge(902): thread name:main
postされたrunnableが、mainスレッド(つまりUIスレッド)で実行されたことが確認できます。
これを、post先を独自のスレッドになるように書き換えてみます。
独自のスレッドにHandlerからpostする
public class SampleHandlerThreadActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btn1 = (Button) findViewById(R.id.button1); btn1.setOnClickListener(new OnClickListener() { public void onClick(View v) { HandlerThread hogeThread = new HandlerThread("hogeThread"); hogeThread.start(); Handler hogeThreadHandler = new Handler(hogeThread.getLooper()); hogeThreadHandler.post(new Runnable() { public void run() { Log.v("hoge", "thread name:" + Thread.currentThread().getName()); } }); } }); } }
実行結果
04-10 15:49:14.966: VERBOSE/hoge(951): thread name:hogeThread
hogeThreadで、postしたrunnableが実行されていることを確認できました。
HandlerThreadはrunの中でLooper.prepare()する
なお、上記のコードではhogeThreadをstartさせてからHandlerをnewしています。HandlerThreadはstartされてからはじめてlooperを自身に設定するためです。
startするまえにgetLooperしてもnullが返るので、new Handler(Looper)は失敗します。
HandlerThreadのrun()
public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }
あわせて読みたい
勉強中: HandlerThread・Handler・Messageの使い方
HandlerThread | Android Developers
IntentService - Boring Days in tech