読者です 読者をやめる 読者になる 読者になる

謎言語使いの徒然

適当に気になった技術や言語を流すブログ。

iPhone で今更 HelloWorld(InterfaceBuilder使わずに実装)

iPhone 日記

Xcode についてくる InterfaceBuilder についての感想の遷移。

①初めて触ったとき → 超意味不。本気で使いにくい。
②すべてプログラムで実装した後 → InterfaceBuilder ってそういうことやってたのね。だがコードで書く。
③今 → とりあえず InterfaceBuilder で見た目だけ作って、動いてからプログラムに置き換える。

順を追って振り返る。
始めに、様々な入門書で InterfaceBuilder を使ったコーディング入門をしている。これは、本に限ったことではなく、Web でもそう。
全く何も知らずに「はいできたー」と、のたまうならそれでも良いかもしれない。しかし本職でそれは致命的だと思う。カスタマイズもできなければ、動的な画面生成もできないからだ。
そして、見事にそれにハマった。InterfaceBuilder で作ったものの実体、どうやって動くのか、それが入門で説明しないからだ。
ごもっとも。そんなの知る必要があるのはこだわりを持つか本職かだけだ。
そこで最初にとった行動は、InterfaceBuilder を使わずにコーディングしてみるということだ。

最初に誰もが通る HelloWorld を僕はこう作った。

  • テンプレートを全部試してみる。
  • 共通部分を比較してみる。
  • 最も基本となるテンプレートからプロジェクトを作成する。

上記の手順で行き着いたのが Window-based Application だった。
main があって、AppDelegate を呼び出して、window と呼ばれる画面が存在するこの構成だ。

構成的には、こんな関係っぽい*1

main -> AppDelegate  <-|  |-  window
             ^         |壁|     ^
      ViewController <-|  |-  UIView

UIViewController には、サブコントローラほ保持できるし、それに合わせて、UIView も下階層で保持できる。
個人的にはあまり深くはしたくないかな、、、、
Window-based Application では、main,AppDelegate,window のセットしかない。

main -> AppDelegate  <-|  |-  window

なので、ViewController は手前味噌で実装した(今はまだしも 09/06 位は殆ど英語しかなかった)。
ヘッダ。

#import <UIKit/UIKit.h>

@interface HelloViewController : UIViewController {
    UILabel* labelHello;
    UIButton* buttonPush;
}

- (IBAction)pushHello;

@end

本体(参照カウントがわかりやすいように手動で数えてます)。

#import "HelloViewController.h"

@implementation HelloViewController

- (id)init {
    if (self = [super init]) {
        // MVC の M 層なんかはこの辺で初期化しとく。
        // 今回はなし。
    }
    return self;
}

// nib(XIB) 抜きでインスタンス作るときのイベント。
// MFC の OnCreate とかそのへん。
- (void)loadView {
    
    // alloc で参照カウント 1 [self addView:]で参照カウント 2
    self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)];
    
    // 参照カウントを 1 にしておく。
    [self.view release];
    
    // alloc 時点で参照カウント 1
    labelHello = [[UILabel alloc] init];
    [labelHello setFrame:CGRectMake(20, 100, 300, 21)];
    [labelHello setText:@"Push button."];
    
    // addSubview で参照カウント 2
    [self.view addSubview:labelHello];
    
    // buttonWithType でボタンを作ると、autorelease なので、
    // このメソッドを抜けると参照カウントが 1 減る。
    // 変数に保持させたいので、retain で参照カウントを増やしておく(参照 2)。
    buttonPush = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
    [buttonPush setFrame:CGRectMake(100, 140, 80, 40)];
    [buttonPush setTitle:@"hello" forState:UIControlStateNormal];
    
    // イベントを設定しておく
    // ボタンを押されたら、自インスタンスの pushHello を起動。
    [buttonPush addTarget:self
                   action:@selector(pushHello)
         forControlEvents:UIControlEventTouchUpInside];
    
    // addSubview(参照 3)
    [self.view addSubview:buttonPush];
    
    // メソッド終了(buttonPush の参照が 2 になる)
}

// ボタン押されたときのイベント。
- (IBAction)pushHello {
    [labelHello setText:@"Hello world!"];
}

- (void)viewDidUnload {
}

- (void)dealloc {
    // 両方参照カウント 1 へ
    [buttonPush release];
    [labelHello release];

    // self.view は消えて、子に持ってる連中の参照カウントが全部 1 減る。
    [super dealloc];
}

@end

で、これを AppDelegate に読ませれば起動できる。

@interface HelloWorldAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    UIViewController* controller; // 追加
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end
- (void)applicationDidFinishLaunching:(UIApplication *)application {    

    // Override point for customization after application launch
    controller = [[HelloViewController alloc] init];
    [window addSubview:controller.view];
	
    [window makeKeyAndVisible];
}

- (void)dealloc {
    [controller.view removeFromSuperview];
    [controller release];
    [window release];
    [super dealloc];
}

で、InterfaceBuilder 使うコードと比較して何が違うかというと、

  • UIViewController 初期化時に、initWithNibName を使用しない(nib/xib ファイルを使わないから)。
  • loadView で画面を生成してる(InterfaceBuilder が本来サポートしてる部分)
  • dealloc で参照カウント制御してる(autorelease しとけば無視もできる)

要するに loadView の所でコード書かなくて済むだけ。
よほど複雑な画面作るのでなければ、コードで書いた方が実行速度早いしおすすめ〜。
とはいえ、見た目に時間かけるより前に、ロジックを確認したい場合も確かにある。なので、最近はこんな使い方をする。

  • InterfaceBuilder で画面を適当にでっちあげる
  • 内部処理がうまい事動いてるのだけ確認する
  • loadView メソッドを作成して、initWithNibName を init に移植する。

もっといい使い方があって、スパゲティにならない方法があれば是非。

*1:実際にはこれも把握するのに2−3日苦しんだのだが