DBチューニングできますか?(5)

せっかくインデックスファイルとメモリについて説明してきたのでついでにプレフィックスインデックスの事をさらっと説明します。

概要

* 目的となるデータを探す場合はインデックスを活用すると効率が良い
* インデックスはその構造上よく使われるデータからメモリ上キャッシュされやすい構造になっています。
* メモリを多く割り当てると、よりたくさんキャッシュできるようにするとさらに効率が良くできます。

とはいえメモリは有限なので、今回は部分的なキャッシュによってインデックスの量を減らす事を考えます。

userIDとPasswordでログインするサービス

最近のWEBサービスではログインIDに自身が登録したemailアドレスが使用できたりします。
ただしemailアドレスの場合は文字列が長くなってしまいがちです。

プレフィックス インデックスという選択肢

インデックスを使って高速にlookupしたいのだけれど、インデックスに長いキーを使ってインデックスファイルの量を圧迫したくないという時に使えます。

注意点

MySQLではorder by,group byクエリではプレフィックスインデックスを使用しません。

DBチューニングできますか?(4)

実はもうちょっとだけ続くシリーズ第四弾です。

概要

MySQLサーバーが目的となるデータを探す場合はインデックスを活用すると効率が良い、という事を前回まとめてみました。
さらにインデックスはその構造上よく使われるルートにより近いノードからメモリ上キャッシュされやすい構造になっていることをまとめました。
という事は、よりたくさんキャッシュできるようにするとさらに効率が良くできます。

そこで今回はよりたくさんキャッシュする方法をまとめます。

my.cnfをチューニングできますか?

MySQLサーバーを起動する際にメモリをどの処理にどれぐらい割り振るか、という選択ができます。
このときにインデックスがメモリにキャッシュされる設定をしておくとよいわけです。

my.cnfには他にも様々な設定ができるのですが、今回はインデックスファイルをたくさんキャッシュするための重要なパラメータについてまとめます。

innodb_buffer_pool_size

innodbMySQLサーバーを立てる場合はこのパラメータを調整すると思えばいいと思います。DB専用機として使うなら最大80%まで割り振れるそうです。

innodb_buffer_pool_size=1GB # 参考値です

こんな感じでしょうか

ダイレクトI/O

前回、ファイルシステムは複数のセクタをまとめて一つのデータブロックとして扱う、という説明をしたと思いますが、ファイルシステムはこのデータブロックをまとめて読み書きし、この単位でキャッシュしたりします。
ページキャッシュのほうがわかりやすいかもしれません。

ただし、これはMySQL側のキャッシュ機構ではなく、OS側のキャッシュ機構です。innodb_buffer_poolでキャッシュしたほうがMySQLに特化したメモリ機構なので効率がよいです。
というわけでOS側がこれをキャッシュしないようにするための設定を行います。

innodb_flush_method=O_DIRECT

こうしておくとよいでしょう。

WEB+DB構成の場合はどうする?

前述の構成は一台のサーバーをMySQL専用機として扱う場合の話ですが、小規模サイトの場合はWEBサーバーとDBサーバーを1台のサーバーに同居させている場合があります。
以下の記事を参考にして下さい。

http://mysql-casual.org/2010/12/mysql-casual-advent-calendar-2010-12-09.html

この場合はMySQLに特化したメモリキャッシュ機構であるinnodb_buffer_poolにメモリまわすより、汎用的なシステムファイルキャッシュにお任せしたほうがよいでしょう。

まとめ

「my.cnfを調整できますか?」「パラメータチューニングはできますか?」という質問ではいろいろなケースが想定されるのでそもそもどのように回答すれば良いのか分かりません。
質問者も「my.cnfいじるとDBが速くなるんじゃないの?」ぐらいの認識だと思います。

「メモリが不足するとスワップが発生するなど速度低下が起こり得えます。そうした場合、ハードウェアの限界を疑う前にパラメータを調査して改善できるかどうか善処します。」

のような感じでとりあえず面接官は納得してくれる、かも

DBチューニングできますか?(3)

概要

前回インデックスを使うとよく使うブロックをキャッシュするから高速になりますよーという内容の記事を書いたのですが、
その前にシーケンシャルアクセスとランダムアクセスの違いを書いてないのに気づいたので順番前後しますがその話です。

シーケンシャルアクセスとランダムアクセスとカセットテープとCD

私がまだ小学生の頃は音楽をカセットテーブで聞いていました。書き込みを禁止する場合はツメを折る、アレです。
自分の聞きたい曲を探すときには早送りボタンを押したままボーとしていたものです。

そのうちCDというものが発売され、好きな曲を瞬時に再生できる、というとんでもなく便利なものが発売されるようになりました。
とても懐かしい話です。この時、2000曲以上を持ち歩く生活を、一体誰が想像できたでしょうか。

CDで好きな曲を瞬時に選択できるは、どこから読めばいいのかを知っているからです。つまりデータを読み取る住所が存在していて、
CDプレイヤーは曲がマッピングされた情報が書かれている場所を調べ、そこから目的の曲がどこから始まっているのかを調べれば良いわけです。

ストリームの向こう側

ここでストリームの概念についておさらいします。以下のコマンドは何をしているのか。このブログをご覧になっているのは技術者ばかりなのでだいたいわかると思います。

% cat hoge.txt

このとき標準出力にはバイト列が送り込まれてきますが、このバイト列はどこからやってくるのでしょう?ちょっとそこを考えてみます。

セクタとブロック

HDD側では読み取り、書き取りする最小単位をセクタと呼び、たいてい512バイトだったりします。

ファイルの中身はこのセクタに保存されます。このセクタがHDD上にたくさんあるので空いているセクタを使ってデータを記憶します。
ただし、私たちはデータの読み書きはファイルシステムを通じて行います。
これはいくつかのセクタをまとめて一つのブロックとして使用します。たいてい4096バイトです。

私のMacだとこんな感じで作成したファイルのst_blksize(ファイルシステム I/O 操作での最適なブロックサイズは4096Byteです。
試しに空のファイルを作成してみます。st_blksize=4096となっています。

% touch hoge.txt                                                         
% stat -s hoge.txt                                                  
st_dev=234881026 st_ino=27489840 st_mode=0100644 st_nlink=1 st_uid=501 st_gid=20 st_rdev=0 st_size=0 st_atime=1311758217 st_mtime=1311758217 st_ctime=1311758217 st_birthtime=1311758217 st_blksize=4096 st_blocks=0 st_flags=0

helloという文字を保存してみます。

% stat -s hoge.txt                                                        
st_dev=234881026 st_ino=27489915 st_mode=0100644 st_nlink=1 st_uid=501 st_gid=20 st_rdev=0 st_size=6 st_atime=1311758459 st_mtime=1311758458 st_ctime=1311758458 st_birthtime=1311758449 st_blksize=4096 st_blocks=8 st_flags=0

st_blocks=8 となっていて複数のセクタが使用されてる事が確認できます。

ランダムアクセスでHDDが目的のセクタにたどり着くには

だいたい次のような公式で表す事ができます。

平均シークタイム + 平均回転待ち時間 + データ転送時間
  • ランダムアクセスでは目的のセクタがどこに保存されているか、位置を知っているのでそこに向かって磁気ヘッドが移動します。
  • 移動が完了したら、磁気ヘッドが待っている場所にセクタがたどり着くまで待ちます
  • 読み取ったデータを転送します。

で、ファイルシステムはそのセクタをさらにブロック単位で扱います。

シーケンシャルアクセスのほうが速い場合もある

実はシークタイムがHDDからデータを読み取る際にコストがかかっています。場合によってはシーケンシャルアクセスのほうが速い事もあります。
次のようなSQLだとランダムアクセスを10000回繰り返すよりもシーケンシャルアクセスのほうが速い可能性があるのでMySQLサーバーのオプディマイザがフルテーブルスキャンを選択する可能性が高いです。

select id, name from user where id < 10000

まとめ

ということで駆け足で大分省略している箇所がありますがインデックスを作成していないテーブルをフルスキャンする場合と、
目的のデータがどこに保存されているかを調べてからランダムアクセスする場合との違いをまとめてみました。

で、インデックスの場合はルートノードやブランチノードはキャッシュの対象になりやすいのでインデックスから目的のデータブロックの位置を知る際にもディスクI/Oをしなくてすむ可能性が高いです。

というわけで今度からは「SQLを書くときは、インデックスを有効活用する事によりシーケンシャルではなくランダムアクセスにより効率よくデータブロックへのアクセスができるように意識して書くように心がけています(キリッ)」と答えてみたいと思います。

DBチューニングできますか?(2)

概要

MySQLサーバを効率よく活用するにはインデックスについて知る必要があると思います。
今回はインデックスを使うとなぜSQLが高速になるのかを考えたいと思います。

ディスクI/Oの回数を減らす工夫

以前ファイルについての記事をちょっと書いたのですが
目的の情報を取得するためにHDDは目的のセクタをめざしてシークします。

この時、目的の情報だけでなく、そのセクタ毎まとめて情報を取得します。
まとめて情報を取得する事によって、一定時間その情報をメモリに保持しておいて同じセクタにシークする必要がないようにできるからです。

キャッシュされやすい仕組みはディスクI/Oを減らす事ができます。
そしてインデックス化されたテーブルはキャッシュされやすい構造になっています。

インデックスの構造

目的の情報を取得するために、概ね次の手順でディスクI/Oが発生します。
下の例はInnoDBでPrimaryKeyで検索したケースです。

  • 最初にルートブロックへアクセス。ブランチブロックの場所を入手。
  • 次にブランチブロックへアクセス。リーフブロックの場所を入手。
  • リーフブロックへアクセス。このブロック内に必要な情報が入っているので丸ごと取り出す

ルートブロックへのアクセスは頻繁に行われるのでよほどひどい設定にしないかぎりキャッシュされるはずですし、ブランチブロックもキャッシュされる可能性が高いです。

クラスタインデックス

primary keyで検索した場合、リーフブロックにアクセスした段階で次のようなデータが格納されています。

PK 列値
1 cola='a1', colb='b1', ...
2 cola='a2', colb='b2', ...
3 cola='a3', colb='b3', ...

さて、次のようにPKを条件句に指定して検索をしたとします。

select * from table where pk = 1

この場合、必要な情報を入手する為に、次のような感じでルートを経由します。

  • PKのルートブロックにアクセス
  • PKのブランチブロックにアクセス
  • PKのリーフブロックにアクセス

もし、ルートブロックとブランチブロックがメモリにキャッシュされていればディスクI/Oは1回で済みます。

セカンダリインデックス

セカンダリインデックスのリーフブロックは次のようにPKの値を格納しています。

Key1 PK
1 10
2 5
3 1

次のようにKey1を条件句に指定して検索をしたとします。

select * from table where key1 = 3

この場合、必要な情報を入手する為に、次のような感じでルートを経由します。

  • key1のルートブロックにアクセス
  • key1のブランチブロックにアクセス
  • key1のリーフブロックにアクセス
  • PKのルートブロックにアクセス
  • PKのブランチブロックにアクセス
  • PKのリーフブロックにアクセス

もし、key1,PKともにルートブロックとブランチブロックがメモリにキャッシュされていればディスクI/Oは2回で済みます。

まとめ

前回の記事では 「高速なSQLが書けますか?」という問いに対して適切なクエリを書けているか、explainによる検証によって客観的に妥当なクエリを書けるという返事をできるようになりました。

今回の記事では「それだと何故高速なのですか?」という問いに対しても返事ができるようになったと思います。
innodb_buffer_pool_sizeを最大80%に設定すればいいと言われている理由を聞かれてもこれでばっちりですね。たぶん。

DBチューニングできますか?

あらすじ

面接に行くとDBに関しての知識はどれぐらいあるのかを問われる事が多々あります。
それは良いのですがその質問がざっくりとしている事が多々あります。たとえば次のように広い質問をうけたりします。

「DBのチューニングはできますか?」

何を基準にした質問なのか、何を基準にして答えればよいのかいつも困るので事前に回答をまとめてから面接に行くとしようという試みです。*1

そもそもDBのチューニングって何?

DBのチューニングできますか?と言われて困ったのは、「相手が期待している回答は何?」という事です。

前提が異なる両者が会話をしても収束しないのでまずは言葉の定義を明確にしたいと思います。

ちなみにこの質問に「ファイルに保存されている情報をより少ない手順で引き出せるか、という質問なのでしょうか?」と逆に質問したところ相手も困惑していました。

今思えば「WEBアプリ作って運営してるとしょっちゅうDBが問題引き起こして困っているんですけど何とかしてくれますか?」という趣旨の質問だったのかもしれません。

とりあえず今回の記事では面接官「この人なら、ひょっとして何とかしてくれるかも!!」と思ってもらえるように理論武装してみようと思います。

これがこの記事の趣旨です。

高速なSQLは書けますか?

これも実際に言われた事があります。この時は次のように答えました。

私「人並みに書けるつもりです…」

この時、私の頭の中はこうなっていました。

私「(必要な情報が格納されているファイルのシークポイントを迅速に発見できるようにインデックスを使ってるっていうのは理解してるつもりではある…)」
私「(そのようにSQLを発行しているかどうかはだいたいイメージできるし実行計画はexplainつけてあげればいいわけだし…)」
私「(まあ何にも分かっていない人が書くよりは妥当なSQL書くとは思うけれど…)」
私「(要するに)人並みに書けるつもりです」

ただし、このやりとりでは誰も得しませんでした。

これは完全にこちらの失敗でした。

「WEBアプリ作って運営してるとしょっちゅうDBが問題引き起こして困っている」人の悩みを
「この人なら、ひょっとして何とかしてくれるかも!!」というふうにを解消していません。

というわけでSQLをおさらい

求めている結果を得るために、最も効率よくSQLを書く、これもDBチューニングの一種だと思います。
というわけで高速なSQLと低速なSQLの違いを説明できるように理論武装してみます。

まずSQLを発行してから、実際に実行されるまでの手順は以下のとおりです。

  • クライアントがSQL文をサーバーに送信する
  • サーバーがクエリキャッシュをチェック。キャッシュヒットした場合はそれを返す。キャッシュミスした場合は次のステップへ
  • サーバーがSQL文を解析、それからどのように処理すれば高速なのかをMySQLが考えて、クエリ実行プランを作成。
  • クエリ実行プランを実行
  • サーバーが結果をクライアントへ返す。

※実践ハイパフォーマンスMySQL第2版の4.3区襟の実行の基礎より

親切なMySQLサーバは、受け取ったSQLを効率よく処理する方法を考え、その後に実行します。

明るい実行計画

MySQLはクエリを実行する前にどのようにファイルアクセスすればよいのかを考える特性があります。

たとえば目の前に100個の箱が並んでいて、その中に1〜1000までの数字がひとつだけ書かれた紙が入っています。300という数字が書かれた箱を探して下さい。
という指令がはいったとします。

さて、あなたはどうやって探しますか?私は全部の箱を開きます。*2

箱は横一列に並んでいて、左側の箱は右側の箱よりも小さい数字が必ず書かれている、という約束事があれば真ん中の箱、つまり左から50番目を開きます。
もし345という数字が入っていれば次は左から25番目を開くでしょう*3

というわけでデータがどのように格納されているのか、知るものと知らざるものでは仕事量が異なります。
なのでMySQLサーバーはクエリを解析し、必要な情報を取得する作業を実行する前に、どのように取得するのかを計画します。

実験準備

テスト用のデータを準備します。

% curl -O http://downloads.mysql.com/docs/sakila-db.tar.gz
% tar zxvf sakila-db.tar.gz 
% mysqladmin -uroot -p create sakila_db
% mysql < sakila-db/sakila-schema.sql -u root -p
% mysql < sakila-db/sakila-data.sql -u root -p

filmテーブルのschema

mysql> show create table film\G
*************************** 1. row ***************************
       Table: film
Create Table: CREATE TABLE `film` (
  `film_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `description` text,
  `release_year` year(4) DEFAULT NULL,
  `language_id` tinyint(3) unsigned NOT NULL,
  `original_language_id` tinyint(3) unsigned DEFAULT NULL,
  `rental_duration` tinyint(3) unsigned NOT NULL DEFAULT '3',
  `rental_rate` decimal(4,2) NOT NULL DEFAULT '4.99',
  `length` smallint(5) unsigned DEFAULT NULL,
  `replacement_cost` decimal(5,2) NOT NULL DEFAULT '19.99',
  `rating` enum('G','PG','PG-13','R','NC-17') DEFAULT 'G',
  `special_features` set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`film_id`),
  KEY `idx_title` (`title`),
  KEY `idx_fk_language_id` (`language_id`),
  KEY `idx_fk_original_language_id` (`original_language_id`),
  CONSTRAINT `fk_film_language` FOREIGN KEY (`language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_film_language_original` FOREIGN KEY (`original_language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

explainを使うとオプティマイザがクエリからどのような方法を使って情報を引き出そうとしているかを知る事ができます。
filmテーブルから全てのカラムを取得する場合と、indexが生成されているカラムだけを取得する場合を比較してみます。

ちなみにInnoDBです。

explainからカバリングインデックスを使っているかを調べる

「いつそんなクエリ使うんだ」という気もしますが、次のクエリをご覧下さい。

mysql> explain select film_id from film where film_id=1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 2
          ref: const
         rows: 1
        Extra: Using index
1 row in set (0.00 sec)

次のクエリは良くあるパターンかな。

mysql> explain select * from film where film_id=1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 2
          ref: const
         rows: 1
        Extra: 
1 row in set (0.00 sec)

このSQLの共通点はtype列がconstになっている点です。PRIMARY KEY/UNIQUEキーを条件に指定しているとマッチするレコードが1つしかない上に順番が規則的なので高速に検索する事が可能です。

異なっている点はExtra列です。

軽くおさらいすると、indexedされているカラムを持つテーブルは、テーブルの情報を格納しているファイルとは別にインデックスファイルを持っています。
そしてこのインデックスファイルを先に調べてからテーブル本体の情報を格納しているファイルを見に行きます。

ただし、最初のクエリはインデックスファイルを見た時点でもうid:1という情報を知っているのでわざわざテーブル本体の情報を読む必要はありません。
この時Extra: Using indexとなり、これをカバリングインデックスと呼んだりします。

カバリングインデックスを活用してみる

titleを検索条件に指定しておいて、マッチするfilm一覧を表示したいケースを考えます。無難に次のようなクエリを発行してみます。*4

mysql> explain select * from film where title LIKE 'J%'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film
         type: range
possible_keys: idx_title
          key: idx_title
      key_len: 767
          ref: NULL
         rows: 20
        Extra: Using where
1 row in set (0.00 sec)

もしカバリングインデックスを知っていて、かつその一覧表示で必要なのはtitleと詳細画面へ遷移するためのfilm_idだけだとすると、次のようなクエリを思いつく事ができるでしょう。

mysql> explain select film_id, title from film where title LIKE 'J%'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film
         type: range
possible_keys: idx_title
          key: idx_title
      key_len: 767
          ref: NULL
         rows: 20
        Extra: Using where; Using index
1 row in set (0.00 sec)

まあ…ちょっとだけ速くなるんじゃないですかねぇ。

explainからインデックスが使えていないクエリに気づいてみる

できれば改善したいのがtype: ALLです。actorテーブルは名字にインデックスをつけているのですが名前にはインデックスがついていません。

mysql> show create table actor\G
*************************** 1. row ***************************
       Table: actor
Create Table: CREATE TABLE `actor` (
  `actor_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `first_name` varchar(45) NOT NULL,
  `last_name` varchar(45) NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`actor_id`),
  KEY `idx_actor_last_name` (`last_name`)
) ENGINE=InnoDB AUTO_INCREMENT=201 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

名前で検索してみます。

mysql>  explain select * from actor where first_name LIKE 'J%'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: actor
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 200
        Extra: Using where
1 row in set (0.00 sec)

mysql> SHOW STATUS LIKE 'last_query_cost'\G
*************************** 1. row ***************************
Variable_name: Last_query_cost
        Value: 40.999000
1 row in set (0.00 sec)

名字で検索したほうが速いです。

mysql>  explain select * from actor where last_name LIKE 'J%'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: actor
         type: range
possible_keys: idx_actor_last_name
          key: idx_actor_last_name
      key_len: 137
          ref: NULL
         rows: 7
        Extra: Using where
1 row in set (0.01 sec)

mysql> SHOW STATUS LIKE 'last_query_cost'\G
*************************** 1. row ***************************
Variable_name: Last_query_cost
        Value: 10.809000
1 row in set (0.00 sec)

ちなみにこのLast_query_costセッション変数はオプティマイザがクエリを実行するために約10ページのランダムデータを読み取るだろうという見積もりだそうです。

type: Allは最もコストがかかっている検索方法です。この場合はインデックスが適切かどうか、
必要ないカラムを読んでいないか、要件を満たすためにどうしてもそのクエリでないと駄目なのかと言った事を検討するとよいかもしれません。

インデックスは絶対ではない

次のようなクエリでは仮にsexカラムにインデックスを使っていても、MySQLはtype: ALLのフルテーブルスキャンを採用する可能性が高いです。

select * from Employee where sex = 'male';

列のデータ種類が、テーブルのレコード数に比べて少ない状態をカーディナリティが低いといいます。
この状態だとMySQLサーバーは「フルテーブルスキャンのほうが速い」と判断します。

MySQLサーバ

今日のまとめ

インデックスについて少し学習しておくと、SQLパーサがどのようなクエリ実行計画をたてるのかをある程度予測する事ができます。
それはexplain句を使う事によってある程度SQLパーサがどうしようとしているのかを知る事ができます。

「速いSQLを書けます」と答えるかはともかく
「処理に時間がかかっているクエリをMySQLがどのような実行計画を立てているかを調べ、そこから適切なインデックスが使用されているのか、無駄となるクエリを発行していないかを調査できます」
ぐらいは答えられるかな?

これだけでDBチューニングができるわけではないですが「人並みに書けるつもりです…」よりは大分ましになった気がします。^^

*1:仕事は準備で8割が決まるんだぜ

*2:300という数字が何個あるのかわからないからです

*3:ご存知2分検索です

*4:実用的な例思いつかないので分かりやすい例って事で

iPhoneでIB使わないでHelloWorldする手順まとめ

久しぶりにxCodeを使おうとするといろいろ忘れてしまっていたので備忘録を記します。

試した環境

% sw_vers
ProductName:	Mac OS X
ProductVersion:	10.6.7
BuildVersion:	10J869

xCodeのバージョンは3.2.6 xCode4は有料なのでまだ躊躇しているっていう。

Developer Information:

  バージョン:	3.2 (10M2518)
  場所:	/Developer
  アプリケーション:
  Xcode:	3.2.6 (1761)
  Interface Builder:	3.2.6 (851)
  Instruments:	2.7 (3017)
  Dashcode:	3.0.2 (333)
  SDK:
  Mac OS X:
  10.5:	(9L31a)
  10.6:	(10M2518)
  iPhone OS:
  4.3:	(8F190)
  iPhone シミュレータ:
  3.2:	(7W367a)
  4.0:	(8A400)
  4.1:	(8B117)
  4.2:	(8C134)
  4.3:	(8F190)

IB使わないでHelloWorld

新規プロジェクトでview-based Applicationを作成します。
私は~/project以下で作業する習慣があるのでそこにHelloWorldというプロジェクトを作成しました。

最初は以下のようなディレクトリ構成になっています。

% ls ~/project/HelloWorld 
Classes                         HelloWorld_Prefix.pch
HelloWorld-Info.plist           MainWindow.xib
HelloWorld.xcodeproj            build
HelloWorldViewController.xib    main.m

xibファイルを削除

xibフィアルはInterface Builderのデータファイルですが、私はIBを使わないでiPhoneアプリを開発したいです。
よってこのファイルを削除します。

MainWindow.xibとHelloWorldViewController.xibを削除してください。以下、作業後のディレクトリです。

% ls ~/project/HelloWorld
Classes                 HelloWorld.xcodeproj    build
HelloWorld-Info.plist   HelloWorld_Prefix.pch   main.m

試しにこの状態でビルドと実行を行ってみます。この状態だとエラーになるはずです。
コンソールに理由が表示されているので確認できます。

reason: 'Could not load NIB in bundle: ...

xibファイルを使う宣言を取り消す

HelloWorld-Info.plistを開いて一番下にあるMain nib file base nameの行を削除します。

xibファイルが行っていた事を代行する

次のmain.mの次の行を書き換えます。

int retVal = UIApplicationMain(argc, argv, nil, nil);

次のようにします。

int retVal = UIApplicationMain(argc, argv, nil, @"HelloWorldAppDelegate");

xibファイルが適切なDelegateクラスを取得してくれていたのですが、そのxibファイルがなくなっているため自分で適切なDelegateクラスを指定する必要があるからです。

HelloWorldAppDelegate.hを書き換える

#import <UIKit/UIKit.h>

@class HelloWorldViewController;

@interface HelloWorldAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    HelloWorldViewController *viewController;
}

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

@end

次のように書き換えます。

#import <UIKit/UIKit.h>

@class HelloWorldViewController;

@interface HelloWorldAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *_window;
    HelloWorldViewController *_viewController;
}

@end

HelloWorldAppDelegate.mを書き換える

#import "HelloWorldAppDelegate.h"
#import "HelloWorldViewController.h"

@implementation HelloWorldAppDelegate

@synthesize window;
@synthesize viewController;


#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    
    // Override point for customization after application launch.

	// Set the view controller as the window's root view controller and display.
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    return YES;
}

(中略)

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

次のように書き換えます。

#import "HelloWorldAppDelegate.h"
#import "HelloWorldViewController.h"

@implementation HelloWorldAppDelegate

#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    
	HelloWorldViewController * helloWorldViewController = [[HelloWorldViewController alloc] init];
	
	CGRect frameForWindow = [[UIScreen mainScreen] bounds];
	_window = [[UIWindow alloc] initWithFrame:frameForWindow];
	[_window makeKeyAndVisible];
	[_window addSubview:helloWorldViewController.view];

    return YES;
}

(中略)

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

helloWorldViewController.mを書き換える

コメントアウトされている箇所を書き換えます。

/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
}
*/

次のようにします。

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
	
	UILabel * label = [[UILabel alloc] initWithFrame:self.view.bounds];
	label.text = @"Hello, world!";
	label.textAlignment = UITextAlignmentCenter;
	label.backgroundColor = [UIColor whiteColor];
	label.textColor = [UIColor blackColor];
	label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
	[self.view addSubview:label];
	[label release];
}

画面にHelloWorldが表示されれば終了です。お疲れさまでした。

Hachiojipm #6

Hachioji.pm #6

というけでカレーを食べに行きました。

http://atnd.org/events/17018

ごはん

貸し切りのはずが人数不足に陥った為、[twitter:@uzulla]さんの奥さんが代打出場してくれてました。

カレーダイニングvery well

ちなみに僕は道に迷ってしまい何度もタイラーンナーにたどり着くという怪奇現象に遭遇しました。
そのあと[twitter:@toku_bass]さんも同じ現象に遭遇し、タイラーンナーまで迎えにきてもらっていました。

誰だ八王子に石兵八陣をつくったのは…

LT

[twitter:@umeyuki]さん

前回はあまりお話できなかったのですが、ちょっと話できました。お仕事はWindows屋さんだそうです。
夜がトクいな方のために、トクいでナイト

夜間蓄熱式機器を使用している人は得ですw

[twitter:@mgiken]さん

初のGANCアプリ(?)であるBLOGをひっさげてきました。
今度は是非入門サイトを作成してほしいです。

記憶なし

えーと、あれ、途中で3名分のLTの記憶がないな?なんでだろう。

[twitter:@ytnobody]さん

Web::Scraperは便利だけどモジュール使い過ぎなので正規表現を使えば無駄なモジュールは要らないっていうメモリと起動時間のエコを提案。
本日の数少ないPerlネタ

[twitter:@nekoya]さん

メモリは割と足りてるけれどもURI::Escapeで結構CPU処理が使われちゃうからURI::Escape::XSいいよ、っていうCPUのエコを提案。さらにURI::XSが何故無いのだろうとおっしゃてました。
本日もっともまともで純粋なPerlのネタ発表。Good job!

[twitter:@okamuuu]俺のターン

いまはhtml+css+jsなお仕事してますが、複数人で作業するとコーディングの流儀が違いすぎてうえってなるので少しでも流儀を似せるためにFrameworkを探している俺がみつけてknockout.js。
これはMVVMというパターンを採用していますが、これはSilverlightが同じパターンらしいです。

発表資料

[twitter:@shinpei_cmyk]さん

ホスティングサーバーのお引っ越しをどうしようかという相談。アプリケーションサーバーは引っ越し簡単。ただしDBサーバーは最新の書き込みが発生し続けるので引っ越しする時どうしましょう。っていう話、だったかな。
[twitter:@nekoya]さんが同様の作業を経験していたのでその時の経験をお話してました。参考になったようです。

すごい暑がりやさんなので夏が心配との事です。

[twitter:@hide_o_55]さん

groongaに認証機能をつけるApp::Ggroonga::Wrapperを発表。
おっと出ましたPerlネタ。認証機能に特化するのか、Groongaに対してさらにwrapしていくのか気になる。今個人でgroongaを使ったBlogを作っている最中なのでそのへんももっと質問しておけばよかったなー。

[twitter:@norry_gogo]さん

バージョン管理ツールやテストコード、作業の自動化というエコが行われていない組織に属する勇者の歴史を紹介していました。

[twitter:@uzulla]さん

このイベントの主催者です。cloud9のサービスで使われるブラウザベースのエディタがいけてるんじゃないのか、という話です。
前回のphpforgeに続きcloud9のデモを披露。

結構有用な開発ツールの可能性は感じますが、もっとドキュメントがあればいいなー

[twitter:@maka2_donzoko]さん

遅れない自信があると言っていたのにあえて遅れて登場して、お約束を守ってくれているっていう。

「エコがテーマのLTでLTしないのがエコだなんてのは誰でも思いつくから負け」という発言に約3名下を向くっていう。
誰もが思いつかないネタで勝負するのが彼の流儀なのです。

Air Hachiojipm [twitter:@hirobanex]さん

PerlモジュールFurlから学ぶ省エネコーディング | hirobanex.net

移動を省略するっていう、もしかしたら今回もっともエコを実践したのかもしれません。

conetntってTypo?

まとめ

HachiojipmはPerlの話をしていないとか、居酒屋でLTとか、なにそれ?などという半匿名のご意見があったそうですが、今回は出席者13人中なんと3名(AirHachiojipmを含むと4名)もPerlの話をするという豊作な回でしたw

個人的な感想ですけど「Shibuya.pmに行って(話だけ聞いて)きました」。というよりも「Hachioji.pmいってきてLTやってきました。」のほうがエンジニアっぽくていいんじゃないですかねえ。
だからみんなもっとHahiojipmにくればいいんじゃないかな。

※まあ他所でLTやってる人からしたら、そのLTと俺たちのLTを同じにしないでよ!って事でしょうけどw