2012/04/02

インターフェースビルダーを使わずに画面を生成:Objective-C

画面が表示されるまでの内部的な動きを理解するために、インターフェースビルダーを使わずにベタ書きで画面を生成してみる。

画面を描画するためには大きく分けて次の処理が必要になる。
1.ウィンドウの生成と初期化 2.ビューの生成と初期化 3.ウィンドウにビューを追加して表示
全体像としては、ウィンドウは基本1つだけ存在し、その上に複数のビューとコンポーネントが乗る、というイメージ。 ウィンドウの生成やビューの呼び出しはAppDelegateファイル内に記述する。実際のビューの中身についてはビューを描画するクラスを作って別途定義する。 AppDelegateファイルの全体像は下記。
#import "StudyAppDelegate.h"
#import "HelloWorld.h"

@implementation StudyAppDelegate

@synthesize window = _window;

- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];  //ウィンドウの生成、初期化
    _window.backgroundColor = [UIColor whiteColor];  //背景色の指定
    [_window makeKeyAndVisible];  //ウィンドウの表示

    CGRect bounds = [[UIScreen mainScreen] bounds];  //スクリーンサイズの取得
    bounds.origin.y += 20;  //ビューの配置場所を指定
    bounds.size.height -= 20;  //ビューのサイズを指定
    UIView* view = [[[HelloWorld alloc] initWithFrame:bounds] autorelease];  //ビューを生成
    [_window addSubview:view];  //ビューの表示
    
    return YES;
}

- (void)dealloc {
    [_window release];
    [super dealloc];
}

@end
1行ずつの処理の内容を羅列すると、
・起動直後に呼ばれるメソッドで下記を処理 - ウィンドウの生成、初期化 - 背景色の指定 - ウィンドウの表示 - スクリーンサイズの取得 - ビューの配置場所を指定 - ビューのサイズを指定 - ビューを生成 - ビューの表示 - メソッドの返り値としてYESを返す。 ・メモリ開放のメソッドを書く。
1行ずつ見ていく。 まずは土台となるウィンドウを作るところから。

ウィンドウの生成と初期化

_window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
まずはUIWindowクラスのallocメソッドでメモリを確保して、initWithFrameメソッドで初期化。初期化時に画面サイズを指定したいので、UIScreenクラスのmainScreenメソッドでメインスクリーンのオブジェクトを取得し、boundsプロパティで画面サイズを取得する。今回はウィンドウサイズ=スクリーンサイズとする。ここまででウィンドウの生成と初期化が完了。

ウィンドウ背景色を指定する

_window.backgroundColor = [UIColor whiteColor];
インスタンス変数_windowのbackgroundColorプロパティに[UIColor whiteColor]を代入すると背景色が指定できる。 この時点でウィンドウの準備が完了したので、

ウィンドウを表示する

[_window makeKeyAndVisible];
つぎはウィンドウの上に乗せるビューの生成と初期化。 ビューの配置する場所や大きさを事前に指定するところから。

改めてウィンドウのサイズを取得しておく

CGRect bounds = [[UIScreen mainScreen] bounds];
CGRect型の変数boundsを用意しつつ、再度[[UIScreen mainScreen] bounds]でメインスクリーンのサイズを改めて取得。(ウィンドウ生成する前の時点で変数を用意して格納しておけば2回記述する必要ないな、、) ※サイズ指定(CGRect CGPoint CGSize) - iPhoneアプリ開発の虎の巻 : http://goo.gl/rJR6H

ビューの配置場所を指定する

bounds.oringin.y += 20;
左上の頂点を始点としてピクセル単位で指定する。メニューバーの下から表示するように値に20を足す。

ビューの縦の大きさを指定する

bounds.size.height -= 20;
ビューを配置する位置を20ピクセル下に移動したので、ビューの全体の縦の大きさは20ピクセル小さくする。(そうしないと画面からはみ出してしまうので。)

ビューの生成と初期化

UIView* view = [[[HelloWorld alloc] initWithFrame:bounds] autorelease];
HelloWorldクラスのインスタンスを生成して、配置場所と大きさを格納したbounds変数を引数として初期化する。 HelloWorldクラスはビューの詳細について書かれているのかな?という感じがする。実際に見てみると、、
#import "HelloWorld.h"

@implementation HelloWorld

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor purpleColor];
    }
    return self;
}

- (void)drawRect:(CGRect)rect {
    UIFont* font = [UIFont systemFontOfSize:24];
    [@"Hello, World!" drawAtPoint:CGPointMake(0,0) withFont:font];
}
@end
こんな感じ。詳しく見ていく。

initWithFrame:メソッドをオーバーライド

- (id)initWithFrame:(CGRect)frame {
}
HelloWorldクラスはUIViewクラスを継承しているので、initWithFrame:メソッドをオーバーライドすることによって初期化処理ができるようになる。 次に、初期化処理の具体的な内容。

親クラスの同名メソッドを読んでオブジェクトを生成してプロパティを指定

self = [super initWithFrame:frame];
if (self) {
    self.backgroundColor = [UIColor purpleColor];
}
return self;
ビューを作ったら何か表示しましょう、ということで描画メソッドを定義する。 HelloWorldクラスはUIViewクラスを継承しているので、drawRect:メソッドをオーバーライドして描画処理を実行できる。

drawRect:メソッドをオーバーライド

- (void)drawRect:(CGRect)rect {
}
このdrawRect:メソッドはビューの再描画が必要なタイミングで自動的に呼び出される。

文字列を描画してみる

UIFont* font = [UIFont systemFontOfSize:24];
[@"Hello, World!" drawAtPoint:CGPointMake(0,0) withFont:font];
1行目でフォントサイズの指定用にUIFont型のfont変数にフォントサイズを格納し、2行目でNSStringクラスのdrawAtPoint:withFontメソッドで文字列を描画する。 ちなみにsystemFontOfSizeメソッドはUIFontのクラスメソッド。また、drawAtPointの引数はCGpoint型で引き渡す必要があるため、CGpointMake関数を使ってCGPointを生成している。 HElloWorld.mが確認できたのでAppDelegate.mファイルの続きに戻る。

ビューを表示する

[_window addSubview:view];
addSubviewメソッドでウィンドウにビューを追加する。 ここまでが画面描画までの一連のプロセス。

2012/03/27

いろいろ用語集

tmux

サーバー側でターミナルを起動して、そこに対してリモートから接続するイメージ。
もし本番環境で作業中にネットワークが切れても大丈夫。
スクリーンの縦分割がデフォルトでできるというのが使いやすいところ。

※tmux : http://goo.gl/DlKn

クロスサイトリクエストフォージェリ対策

昔mixiで話題になったぼくはまちちゃん、がそう。

1. 攻撃者が、攻撃用の Web ページを作成して WWW 上に公開する。(WWW上ででなくとも、未対策のHTMLメーラーにウェブページをHTMLメールとして送信するだけでもよい。)
2. 第三者が、攻撃用の Web ページにアクセスする。
3. 第三者は、攻撃者が用意した任意の HTTP リクエストを送信させられる。

4. 送信させられた HTTP リクエストによって、攻撃者の意図した操作が行われる。 (ここで「第三者」とは、被攻撃サイトに意図せずアクセスさせられると言う意味で用いている。)

防ぐ方法としては一連の流れを紐付けるようにセッション情報を紐付ける、という方法がある。

Railsではライブラリとして提供されているものがあるので、それを組み込む方法で対応したほうが自前で作成するよりも信頼性が高い。

クロスサイトリクエストフォージェリ - Wikipedia : http://goo.gl/bFhn

カピストラーノ

クライアントからSSHでサーバーにリモートアクセスしてシェルを実行するどいう動作をRubyをつかって自動化できるツール。
元々はRailsのデプロイの緩和のために作られたが、他にも色々と使える。

カピストラーノタスクはRubyで記述する。また、カピストラーノはcapfileから指示を読む。capfileはただのテキストファイル。

capコマンドで実行できる。また、役割を与えることでサーバーごとに処理を変えることもできる。

※カピストラーノ(Capistrano)を使ってみよう!(Basic編) : LR Labs : 株式会社ライブレボリューション アリエスユニット : http://goo.gl/SHjeF

chef-solo

chefもあるが、chefサーバーを立てないといけないので、chef-soloが比較的お手軽な感じ。
Rubyでレシピを書ける、というのがいいらしい。

※chef-soloで作業環境構築の自動化 | ひげろぐ : http://goo.gl/MfE3g

IRC【Internet Relay Chat】

基本的にはチャットシステム。Skypeなどとは違い、ID認証を必要とせずサーバー情報をIRCクライアントに指定するだけで接続することができる。また、基本は多人数でのテキストチャットがメイン機能。
IRCのサーバー側はLinuxベースでの構築が必要。
会社内や学校内のサーバーにIRCを構築してクローズドのチャットシステムを作ることができる。

※IRCとSkypeの違いってなんですか? - Yahoo!知恵袋 : http://goo.gl/IJ94V

継続的インテグレーション (Continuous Integration, CI)

継続的インテグレーションとは、ソフトウェアの品質改善・納期短縮のためのソフトウェア・エンジニアリングの習慣の集合である。その原則は、開発の連続的な全行程が終わってから品質管理を行うという古い慣行をやめ、成果物の諸小部分に対して頻繁に品質管理を行うことである。

※継続的インテグレーションとは - はてなキーワード : http://goo.gl/sY1yw

Jenkins

CIツール。
ソースコードの統合、テストの自動化、メトリクスの自動取得、ビルド完了時の画面を変えたりなどの愉快な機能もw
詳細は下記。

※「Hudson」改め「Jenkins」で始めるCI(継続的インテグレーション)入門 (1/4) - @IT : http://goo.gl/6F4d7

リファレンス

「変数を参照するもの」の意味。参照したい変数のアドレスのことで、C言語ではポインタと呼ばれたりする。
ただしC言語のポインタと同じに考えてはいけない。微妙に別のものである。
このリファレンスを通して、参照している変数を呼び出すことをデリファレンスという事も合わせて覚えておきたい。

※リファレンスとは - はてなキーワード : http://goo.gl/Dr8vb

ガングリア

下記のdstatと同様、サーバーリソースをグラフ化してくれるツール

dstat

サーバーリソースをグラフ化してくれるツール。
他にもいくつかツールが有るようだが、色々と統合して表示してくれる、ということで便利らしい。

※dstatの万能感がハンパない - (ひ)メモ : http://goo.gl/ETRui

iostat -dkxではIOPSやバイト単位の値など、I/Oに関する詳細は確認できますが、CPU使用率など他の統計値は同時に見ることができません。
vmstatはメモリの状況やブロック数単位のI/O状況は見られますが、バイト単位のI/O状況やネットワークの送信、受信バイト数を見ることはできません。
ターミナルを複数上げてvmstatとiostatをそれぞれ実行してもいいのですが、できれば一つのターミナルでいろいろな値を見たい。というわけで、dstatの出番です。
□MTGでの会話メモ IOスタットとVMスタットを合わせた感じ
1秒ごとにモニタリングしてくれる。
最初はガングリアでもいいが、ガングリアは15秒とか30秒ごとに見る感じ。
脳内マージしなくても良くなるのは嬉しい。
方針としてはディスクIOをなくすようにしていくことが目標。

インスタンス変数の定義はプロパティ宣言が自動生成してくれる:Objective-C

いろんなサンプルプログラムを見ているとクラスを定義する際のインスタンス変数の定義部分で色々な定義のされ方があって混乱したので、調べてみた。

そもそもは

インスタンス変数はクラス定義の.hファイル内の@interfaceから始まるコンパイラディレクティブ内の{}の中に記述する、とういうのが基本でした。が、Objective-C2.0からコンパイラがclang(詳細についてはエントリ末尾の参考サイト参照)になって賢くなった、ということらしく、プロパティで変数が宣言されていれば自動生成させるだけでOK、ということになったそうです。

なので、これまではインスタンス変数を宣言しつつ、プロパティでももう一回インスタンス変数についてのプロパティ宣言をしていたのですが、{}内の記述は消しちゃってもいい、といよりもむしろ{}ごと消しちゃいなさい、ということみたいです。具体的には下記みたいな感じです。

これまでは

@interface hogehoge : NSObject
{
    NSString *str1 ;  //インスタンス変数str1を定義
    NSString *str2;   //インスタンス変数str2を定義
}

@property (nonatomic) NSString *str1;  //str1のプロパティ宣言
@property (nonatomic) NSString *str2;  //str2のプロパティ宣言
@end

Objective-C2.0以降は

@interface hogehoge : NSObject
@property (nonatomic) NSString *str1;  //str1のプロパティ宣言
@property (nonatomic) NSString *str2;  //str2のプロパティ宣言
@end
すっきり!

また、上にも書きましたが、基本的にプロパティ宣言で済んでしまった場合は{}も消しましょう、ということみたいです。 iOS Developer Libraryの「Objective-C プログラミング言語」ファイルのP.35にも下記のように書いてあります。
歴史的には、インターフェイスにはクラスのインスタンス変数宣言が必要でした。(中略)インスタンス変数は実装詳細であり、通常、クラス自身の外からアクセスされることはありません。さらに、実装ブロック内に宣言すること、あるいは宣言済みプロパティから自動生成させることも可能です。したがって通常は、インスタンス変数宣言をパブリックインターフェイスで行うべきではないので、波括弧も省略してください。
プロパティ宣言が必要ないインスタンス変数を定義したい場合にはヘッダファイル(.hファイル)に書くのではなくて、実装ファイル(.mファイル)内の@implementation内に{}を追加してその中に記述するのが今風(?)みたいですw


参考サイト

イマドキっ子の Objective-C | cockscomb.info
Objective-C入門その7:アクセサメソッドをマスターする|サルにもできるiPhoneアプリの作り方
Clang - Wikipedia

2012/03/26

ラッパークラスについて:Objective-C

ラッパークラスってなに?

まず前提として、int型等の基本的な変数はオブジェクト化されていません。ただし、Objective-Cで用意されている基本的なクラス群の中には引数を必ずオブジェクト型で渡さないといけないメソッドがたくさん定義されています。

そこで、int型などの数値をオブジェクトとして扱う必要があるため、その時のためにラッパークラスが用意されています。
つまり、ラッパークラスは数値などをオブジェクト化するためのクラス、ということになります。

ラッパークラスNSNumbrerでラップしてみる

まずは
NSNumber *wrappedInt = [NSNumber numberWithInt:123]; //インスタンス生成メソッドを利用してオブジェクト生成
もしくは
NSNumber *wrappedInt = [[NSNumber alloc] initWithInt]];  //イニシャライザを利用してオブジェクト生成
でInt型のラッパークラスのインスタンスを生成する。

※上記の2つにはメモリ管理の視点から見ると違いがあるらしいので、後々勉強が必要。

ラップされた値を取り出す

NSNumberオブジェクトからラップされている値を取り出すときにはいろんな型で取り出すことができる。 たとえば、 [wrappedInt intValue] とか [wrappedInt doubleValue] とか [wrappedInt stringValue] とかで取り出せる。

文字列の生成について:Objective-C

文字列の生成方法について、文字コードの指定や、複数の文字列を統合したい場合などパターン別にまとめ。

1.文字列を直接指定して生成する方法

NSString *str = @"hogehoge"; これは単純でわかりやすいですね。

2.文字列と文字コードを指定して文字列を生成する方法

オブジェクトを生成してからイニシャライザで指定する方法と、クラスメソッドを利用する方法があります。
結論から言うと、クラスメソッドを利用したほうがシンプル、かつ色々と便利です。

オブジェクトを生成してからイニシャライザで指定すると、
NSString *str = [[NSString alloc] initWithCString: "hogehoge" encoding:NSUTF8StringEncoding];
こんな感じ。

クラスメソッドstringWithCString を利用した場合、下記のようになります。

NSString *str = [NSString stringWithCString:"hogehoge" encoding:NSUTF8StringEncoding];

stringWithCString:encoding: メソッドはallocメソッドによるメモリ割り当てからインスタンスの初期化まで全部やってくれるみたいです。
また、ラッパークラスのところでも書きましたが、イニシャライズを利用する場合とクラスメソッドを利用する場合で、メモリ管理の手続きが若干変わるようです。そのあたりについてはまた別に機会にエントリしたいと思います。

3.複数の文字列オブジェクトを組み合わせて文字列を生成する場合

実際にはこの形もよく使いますね。
NSString *str = [NSString stringWithFormat: @"%@ と%@", str1, str2];
1.の方法で下記のように書くとエラーになります。サボってついついやりがち。(私だけ?)
NSString *str = @"%@ と%@", str1, str2;  //これはダメ
ちゃんとstringWithFormatメソッドを使って生成しましょう。