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

sos の 作業メモ

プログラミングや英会話学習、マイルや旅行、日常生活など。最近はWebFormなASP.NETのお守りがお仕事です。

日々の生活にhappyをプラスする|ハピタス Gポイント

Objective-Cフレーズブック 第2章

Objective-C 読書

Objective‐Cフレーズブック―使いこなすためのコード&イディオム100+

Objective‐Cフレーズブック―使いこなすためのコード&イディオム100+

前回の続き

第2章 Ocjective-C入門

  • ObjCはシンプルな言語に見えるが、それは多くのことをライブラリに任せてあるから

Objective-Cの型

  • オブジェクトは必ずヒープに割り当てられ、ポインタによって参照される
  • 全てのオブジェクトのレイアウトは、まずルートクラスのインスタンス変数(ivars)によって定義され、その後サブクラスで定義される。サブクラスでは、新しいインスタンス変数の追加しかできないため、あるインスタンスへのポインタをスーパークラスのポインタへ暗黙的にキャストすることは常に安全である
  • 派生クラスからスーパークラスへは明示的なキャストが必要
  • スーパークラスが応答するメッセージには、全てのサブクラスも応答する
  • ObjCには、新しいポインタ型のidが採用されている。ルールはvoid* と似ており、任意のオブジェクトポインタからidへのキャストや、idから任意のオブジェクトポインタへの暗黙的にキャストも可能
  • メッセージには型情報が含まれず名前で検索されるため、すべてid型だけを使用することも可能。ただ、静的な型情報の指定があれば、コンパイル時に型チェックを行えるので、id型だけでのコーディングはお薦めはしない
  • C++では、オブジェクトが不変(immutable)であることを示すにはconstキーワードを使うが、ObjCでは可変サブクラスパターン(immutableなクラスがmutableなサブクラスを持つ)を使う。可変から不変(サブクラスからスーパークラス)へのキャストは可能だが、逆は不可能
  • Class型はidに似ているが、クラスの参照が可能。クラスはオブジェクトなので、idをClass型の代わりに使う事も可能
  • id,Classの他にSEL型, IMP型が定義されている

メッセージの送信

  • ObjCのCへの最も重要な追加が、オブジェクトへのメッセージの送信。メソッド呼び出しではなく、メッセージを送信してオブジェクトに影響を与える
  • -はオブジェクトのインスタンスへ、+のついたにものはクラスへ送信されるメッセージを示す記号
  • [anObject message]; がメッセージを送信するための構文
  • :(コロン)一つにつき一つの引数をとることが簡単に判別可能
  • nilにメッセージを送信してもエラーは発生せずに0が返る。ただし、構造体を返すメッセージをnilに送信した場合の動作は不定
  • ObjCでは、クラスが自身のインスタンスを作成する責任を持つ。C++のnewに相当するものはない。似たようなものに+newはあるが、言語仕様では処理内容に関しては規定されておらず、単にレシーバーの新しいインスタンスを返すという規約があるだけである

セレクタ

  • ObjCでは、オブジェクトに特定のメッセージを送信するが、このメッセージの名前のことをセレクターと呼び、その型をSEL型とする
  • セレクターは、引数にメッセージ名の定数文字列表現をとる @selector()ディレクティブで作成する
  • セレクターを使用して名前でメソッドを呼び出すことや、セレクターを引数として渡す事もできる
  • ObjCのAppleGNUの実装の大きな違いの一つが、GNUが型付けされたセレクターを使用することにある。GNUのものは引数の型のエンコーディングも含まれているが、Appleのものには含まれていない

クラスの宣言

@interface Integer: NSObject
{
    int integer;
}
- (int) intValue;
- (void) addInteger: (Integer*)other;
@end

@implementation Integer
- (int) intValue { return integer; }
- (void) addInteger: (Integer*)other
{
    integer += [other intValue];
}
@end
  • 新しいクラスの作成には、パブリックインターフェースとそのプライベート実装を記述する
  • ObjCの新しいキーワードは全て@で始まっているが、これはCでこの記号の識別師が許可されていないため
  • ObjCのインターフェースは .h, 実装は .mファイルに格納するが、特に強制されているわけではない
  • インスタンス変数(JavaC++ではフィールドやメンバと呼ばれる)はインターフェースの一部として宣言される。新しいコンパイラでは、@implementationで定義することも可能となっている
  • インスタンス変数は、@publicで外部に公開することも可能ではあるが、一般的には行わない
  • メソッドの引数は、その型にキャストできるものであれば何を渡しても構わない

プロトコルの使用

  • @protocolは、オブジェクトが実装しなければならないメソッドのリストを定義するもの
  • クラスと同じでプロトコルもオブジェクト
  • プロトコルへの参照は、@protocol()ディレクティブで取得できる
  • プロトコルはコンパイル時と実行時に型チェックが行われる。あるプロトコルHogeを実装するオブジェクトは、 id に必ずキャストできる

クラスへのメソッドの追加

@interface NSObject (Logging)
- (void) log;
@end
@implementation NSObject (Logging)
- (void) log
{
    NSLog(@"%@", self);
}
@end
  • ソースコードにアクセスできなくても、カテゴリを使用すれば、既存のクラスにメソッドを追加できる
  • カテゴリ中で宣言されたメソッドは、元のクラスで宣言された同じ名前のメソッドを置き換える。カテゴリが2つあり、その中で同じ名前のメソッドがある場合、どちらが使用されるかは不定
  • カテゴリを利用する事で、クラスにプロトコル準拠を追加することができる
  • 最近のObjCコンパイラでは、クラス拡張の中でインスタンス変数を宣言することや、 @implementationコンテキストでインスタンス変数を宣言することが可能。これらにより、パブリックヘッダーから実体の詳細を隠蔽可能

非公式プロトコルの使用

  • 現在のObjCのプロトコルでは@optionが使えるため、カテゴリを利用した非公式プロトコルの実装の手法は必要なくなった

(宣言された)プロパティを持つメソッドの合成

  • Objective-C 2への大きな追加の一つに、(宣言された)プロパティがある。単にプロパティと呼ぶとインスタンス変数との区別がつかないため、"宣言された"と呼んで曖昧さを回避することもある
  • プロパティは、@propertyディレクティブで定義する
  • プロパティにはドット構文でアクセスが可能。ただし、プリプロセスでアクセサメソッドを使ったメッセージ送信に置換されるだけで、アクセス自体が高速になるわけではない
  • プロパティはデフォルトでatomicだが、もっと上位のロック機構を使う方がよいので、nonatomicで宣言するとよい
  • プロパティは readwriteかreadonlyのどちらかを指定可能
  • @synthesizeディレクティブを使用することで、アクセサメソッドとインスタンス変数を自動的に生成させることができる
  • 新しいXcodeは自動的にアクセサを合成するため、@synthesizeディレクティブの指定は不要である

self,_cmd,super

  • 全てのObjCメソッドは、関数にコンパイルされるが、この関数には、隠し引数としてselfと_cmdが追加される
  • selfはメッセージのレシーバーで、インスタンスメッセージならクラスのインスタンス、クラスメッセージならクラス自身となる
  • C++のthisキーワードはObjCのselfとほぼ等価だが、ObjCのselfはキーワードではなく、引数の名前である点が違う。C++のthisに代入するとエラーになるが、ObjCのselfへの代入は許可される
  • _cmdはメッセージのセレクタ。オブジェクトに対して扱い方を知らないメッセージが送信された時に、メッセージを転送するために使用される
  • super疑似変数は、メッセージのレシーバーとして使用された時にのみ有効。現在のクラスではなく、スーパークラスの中で検索されるメッセージを生成する
  • superへのメッセージの送信では、引数としてobjc_super構造体へのポインタをとるメソッド検索関数がしようされる
  • スーパークラスはコンパイル時に固定される
  • カテゴリでは、コンパイル時にクラス構造を使用できず実行時に検索が行われるため、カテゴリの中で定義されたメソッドから送られるスーパークラスメッセージは遅くなるが、Appleのランタイムでは、リンカに外部クラス参照を解決させて、この問題を解消している
  • 期待通りに動作しないケースが多数あるため、クラスポージング(poseAsClass)の使用は避ける

isaポインタ

  • C++ではクラスは構造体であり、C++のクラスのメソッドの呼び出しは、変換された名前を持ち、最初の隠し引数が構造体へのポインタを持つCの関数が呼ばれることになる。
  • ObjCでは、クラスと構造体はまったく違う。クラスにはメッセージを送信可能だが、構造体には不可能
  • クラスにメッセージを送信可能なのは、クラスの最初のインスタンス変数がクラス構造へのポインタだから。そして、このポインタをisaポインタと呼ぶ
  • isaポインタは、一つのインスタンス変数であり、キーワードではない。
  • isaポインタはClassを指す。これはObjCランタイムヘッダによって提供されるtypedefで、不透明な型かパブリックな構造体へのポインタとして定義される
  • クラス構造の正確なレイアウトがprivateなランタイムでは、isaポインタを検査する為の関数を使って、インスタンス変数のオフセットやメソッドとプロパティの名前と型シグネチャスーパークラスを取得する
  • 他のオブジェクトのisaポインタを直接参照する事も可能だが、最近のObjCコンパイラでは直接参照を非推奨とし、object_getclass()/object_setClass()を使用する必要がある
  • ObjCのオブジェクトはメッセージを転送可能なため、オブジェクトポインタがプロキシな場合もある。別のオブジェクトのクラスを取得する場合は、-classメッセージを送信する。これは低速だが、確実に動作する

クラスの初期化

  • C++では、グローバル変数や静的変数に関数の結果を代入する事により、静的な初期化が可能(mainの前に特別な関数が呼び出される)だが、呼び出される順序は不定のため、正確に使用するのは困難
  • ObjCにも同様のしくみがあり、main()が呼ばれる前に各クラスの+loadメソッドが呼び出される。クラス自体とその全てのスーパークラス、定数文字列ジュラスがロードされていることは保証されるが、その他のクラスがロードされているかどうかは保証されない。自分のマシンでは順序が決まっていても、他のマシンでもそうなとは限らないため、+loadの使用には充分注意をすること
  • +loadより安全な代替手段が+initialize。最初のメッセージがクラスに送信される時に、一度だけ呼び出される

型エンコーディング

  • ObjCのオブジェクトは、メッセージを送信する事でその内側の情報を得ることができるが、Cの型に関してはできない
  • @encode()ディレクティブによって、コンパイラが型エンコーディングを生成する
  • ObjCのオブジェクト: @
  • char* : *
  • 他のポインタ型 : ^と型
  • 配列 : []の中に要素のエンコーディング

Blocks

  • OS X 10.6でC言語に導入された
  • Blockはクロージャであり、ラムダとも呼ばれる
  • Blockを作成するための構文はお粗末で、関数ポインタのためのC構文を基に、*が^で置き換えられている
  • Blockにはisaポインタがあり、メッセージを送信可能

なんとか2章を消化。

思ったよりワクワクしない内容だし、翻訳が雑で内容が頭にちっとも入ってこない。読んでおけば、言語仕様でどはまりするような事態を回避することができるのかもしれないが、書いてある事が直接コーディングに役立つかと聞かれると、さぁ…と答えるかも。

まだObjective-Cの入門の章だからかもしれないため、あと数章読んでみてから、読むのをやめるか判断することにします。

次回へ続く