Objective-C再入門

すっかり忘れてしまっていたので詳解 Objective-C 2.0 改訂版を一通り読んだ。
この本はみんなに勧められるだけありますね。
「こういう問題点があります。だからこうします。」
の流れがわかりやすくて読んでて楽しかったです。
問題となるようなサンプルコードを上手く持ってくるのもすごいなぁと思いました。


ということで、読んだメモ。

インタフェースの概念

  • 重要なのは「使いたいクラスでどういう呼び出しができて、どういう戻りがあるのか」ということ
  • なのでObjective-Cではinterfaceという中でそれを書いている
  • こういう「送信」をしてくれれば、こういう「応答」をしますよ
  • これを.hに書いてくれているので、そのクラスを使う人は.hだけ見ればいい
  • .mでどんな実装してるかなんて興味ない

なんでNSってのが頭に付いてるのが多いの?

  • 元々NeXT社がObjective-CNEXTSTEPというのを作っててそれの略語
  • ついでにnibファイルは何の略?(p.377)
    • Next INterface BuilderでこれもNeXTstep由来
    • xibはXMLファイル

プライベートな名前としての接頭語_

  • Appleが予約しているので接頭語にアンダースコアを付けてはいけない

ヘッダ中の引数や返り値の宣言で、別のクラスの型を使う場合の記述

  • #importでそのクラスのインタフェース部分をimportしても良いが、ヘッダファイルは様々情報が書いてありコンパイル時の処理が重くなることがある
  • 単純に「これはクラス名だよ」とコンパイルに教えたいだけなら@classで宣言してあげれば良い
  • そのクラスのメソッドも使うのであれば、実装部分でヘッダファイルをimportしなければいけない

キャストを使うべき場所

  • スーパークラスの型の変数にサブクラスのインスタンスを代入している場合、そのサブクラスでしか持っていないメソッドを呼んでしまうとエラーになってしまう
  • なので、その部分だけではサブクラスの型にキャストしてあげる必要がある
  • id型を使う方法もあるが、これだと型チェックもしてくれないし意図もわかりづらいので用途に合わせて使う

メソッド内で自分自身のメソッドを呼び出す時などに固定のクラス名を書かない方が良い(p.309)

  • サブクラスが継承していた場合、固定のクラス名を書いてしまうとそのクラスに依存してしまうが、selfにしておけばサブクラス自身を表すことになる

インスタンス変数とアクセサとプロパティ

  • インスタンス変数はインタフェースの{}に書かれる変数のこと
    • 型が自分のクラスの型と同じならば->でアクセスできる
      • たとえばメソッドの引数で受け取る場合は、その引数のクラス名が自分のクラス名と同じ場合のみ使える(スーパークラスやidだとダメ)
    • オブジェクトの情報隠蔽もふまえて考えると、プロパティ(またはアクセサ)を使えばいいので、->は使わなくて良さそう
  • アクセサは->でアクセスしなくていいようにゲッタとセッタのメソッドを用意しておくこと。ちなみにコーディング規約でゲッタの接頭語にgetは付けない。
  • プロパティは「外部からアクセスできるオブジェクトの属性」という意味
    • .(ドット演算子)でアクセサを呼び出せる
      • コンパイラは、型を見てそのプロパティに対するアクセサがあるか判断するため、id型の変数にドット演算子は使えない(p.298)
    • 宣言プロパティ@propertyを使えば、アクセサを自分で作らなくても呼び出せる
      • @propertyは宣言を行うだけなのでプロパティがインスタンス変数で実現されているかどうかは関係ない
    • キー値コーディングを使う方法もある

ドット演算子(p.298-)

  • 通常のメッセージ送信と同じようにチェーンのように並べて書けるが、途中で参照先がnilならば通常のメッセージ送信同様メッセージ式の値はnil
  • selfに対してもドット演算子が使えるが、アクセサ内部で使ってしまうと再帰呼び出しになり停止するので注意
  • 構造体のメンバ参照もドット演算子を使うがセッタメソッドのようには扱えないので勘違いしないように注意
  • あくまでプロパティへのアクセスが役割なので宣言プロパティや典型的なゲッタセッタとして使いましょう

メソッドとnil

  • 値がnilの変数にメッセージを送っても「メッセージはどこにも送られない」(p.63)

@propertyのretainオプションとnonatomicオプション(p.295)

クラスメソッド

  • クラスメソッド実行中のselfはそのクラスオブジェクトを表す

クラス変数

  • static指定子はファイル内部でのみ有効という性質を利用し、static指定子をクラス変数としてる
  • staticだと継承したときにサブクラスからスーパークラスにアクセスできない問題があるが、そもそもアクセサメソッドを用意すべきだし、用意すれば問題ない

動的にクラスを生成

  • AとBというクラスがあったときに、動的にクラスを生成したい時はclassというクラスメソッドを使う
Class theClass = flag ? [A class] : [B class];

動的にメソッドを実行

  • @selectorを使うとメソッド式の内部表現(SEL型)を変数に代入できる
  • またその内部表現はperformSelectorでメッセージ送信できる
  • それらを利用すると以下のように動的に実行できる
SEL method = (cond1) ? @selector(activate:) : @selector(hide:);
id obj = (cond2) ? myDocument : defaultDocument;
[target performSelector:method withObject:obj];

あるインスタンスオブジェクトがあるメソッドを実装しているかどうかの確認

  • respondsToSelectorにSEL型にしたメッセージ式(@selectorで作ったもの)を渡してあげる。YESなら実装してある

あるインスタンスオブジェクトがあるクラスのインスタンスかどうかを確認

  • isMembaerOfClassというインスタンスメソッドがあるのでそれを利用する
BOOL isMember = [someobj isMemberOfClass: [A class]];

メモリ管理

  • オーナーシップ・ポリシーに従ってプログラミングしましょう
    • alloc..., copy..., retain..は、保持したインスタンスオブジェクトが不要になった場合に解放する責任がある
      • release, autoreleaseメッセージを送りオーナーシップを解放する
  • 引数や返り値でオブジェクトを渡す場合はオーナーシップの移動は起こらない
    • ただし、インスタンスを渡された側がそのインスタンスを保持することは自由だし、インスタンスを渡す側はあらかじめautoreleaseによってオーナーシップを放棄してから渡せるので、結果として移動したように見えることもある
  • メソッド内でtmpオブジェクトを作り、それオジェクトを返り値にする場合は、returnのタイミングでオーナーシップを放棄する
id tmp = [[ComplexData alloc initWithData:myValue];
return [tmp autorelease]

保持の循環

  • 保持の循環を避けるため、親子関係なら親は子のオーナーシップを持ち、子はオーナーシップを伴わないようバックポインタというポインタの参照だけにする

オーナーを必要とするインスタンスと、一時的なインスタンス

  • initから始まるメソッドを使って生成されたインスタンスは呼び出した側がオーナーになる
    • オーナーがreleaseの管理をしないといけない
  • 生成されたデータを表す名前(例えばNSStringならstringから始まるクラスメソッド)でのインスタンス生成の場合は自動解放プールに登録済みの一時的なインスタンス
    • 自動解放プールが解放されるタイミングでreleaseされるので管理の必要なし
    • また、一時的なインスタンスを生成するようなクラスメソッドをコンビニエンスコンストラクタという

配列を使う時の注意点

  • インスタンスのオブジェクトを配列の要素にしたタイミングで、配列retainをオブジェクトに送りオーナーシップを持つ
  • なので、オブジェクトを配列に入れるタイミングでautoreleaseをするか、入れた後にreleaseをして、後の管理は配列に任せる(p.204)

フレームワーク(framework)はソフトウェアを構成し、実行するために必要なクラスライブラリやヘッダファイルなどをまとめて一つにしたもの

  • クラスライブラリは実行時に動的にローディングされるように構成されていて、アプリケーションによって共有される

ブロックオブジェクト(p.322-)

  • クロージャのこと(C言語の拡張)
  • C言語の場合「function」や「sub」の用に関数を表すキーワードがないため^を使ってると思えば良さそう
  • 自動変数はブロックリテラルが記述されたタイミングで保持され、ブロックオブジェクト内では参照しか行えない
  • Objective-CでコンパイルしたブロックオブジェクトはObjective-Cのオブジェクトとして振る舞う
  • Objective-Cで扱う場合ブロックオブジェクトをcopyメソッドで複製できるが、その時のリファレンスカウンタの動きには注意
    • ブロックリテラル内に何もオブジェクトを含めない場合、ブロックオブジェクトのカウンタが増える
    • ブロックリテラル内にインスタンス変数を含める場合、selfのカウンタが増える
    • ブロックリテラル内にインスタンス変数でないオブジェクトを含める場合、そのオブジェクトのカウンタが増える

アプリケーション固有の初期設定はmainではなくUIApplicationのデリゲートオブジェクト内に記述(p.391)

  • アプリケーションの起動直後、起動が終わったことを知らせるメッセージがデリゲートに送られるのでその内部に記述する
  • アプリケーションが切り替わったとき、終了する直前に行いたい動作も

アサーション(p.447)

  • デバッグ目的でプログラムが満たしてなければならない条件をプログラムの中に記述し、その条件が破られた時に例外を発生させる仕組み
  • NSAssertを使って真でなければならないはずの条件式を記述する
  • NS_BLOCK_ASSERTIONSというマクロが定義されている時はコードに組み込まれない
    • 完成版のプログラムをコンパイルする時はコンパイラオプションに-DNS_BLOCK_ASSERTIONSを付ける
      • オプション-Dはコマンドラインからマクロ定義するためのもの

GCD(p.472-)

  • それぞれの作業をブロックオブジェクトとして記述しキューに入れるとシステムが最適なスケジューリングで処理してくれる

キー値コーディング key-value coding,KVC(p.514-)

  • アクセサメソッドが実装されてなく、宣言プロパティがなくてもプロパティにアクセスできる
    • 強力な機能なのでその分注意が必要
  • setValue:forKeyを使って値をセット、valueForKeyを使って値をゲットする
    • 受け渡しは全てid型になるのでプロパティに間違った値を渡さないように注意
  • 全てid型でのやり取りになるため、スカラ値などの「オブジェクトでない値」の場合は「セットする時にはラップする」「ゲットはラップしたオブジェクトで戻ってくる」という点に注意