Objective-Cは、真のオブジェクト指向プログラミングをサポートできるように設計されたシンプルなコンピュータ言語です。

Objective-Cは、クラス定義、メソッド、プロパティの構文に加え、クラスの動的拡張能力を高めるその他の構造を提供することにより、標準のANSI C言語を拡張しています。クラスの構文と設計は、最初のオブジェクト指向プログラミング言語の1つであるSmalltalkに基づいています。

これまでに他のオブジェクト指向プログラミング言語を使用したことがあれば、以下の情報はObjective-Cの基本構文を学ぶのに役立ちます。カプセル化、継承、ポリモーフィズムなど、多くの伝統的なオブジェクト指向の概念がObjective-Cに反映されています。ここにはいくつかの重要な違いがありますが、これらの違いはこの記事で説明されており、必要に応じてさらに詳細な情報もあります。

これまでにプログラミング言語を使ってプログラムを作成したことがない場合は、少なくとも開始する前に、関連する概念について基本的な理解が必要です。オブジェクトの使用とオブジェクト指向アーキテクチャはiPhoneプログラミングの基礎であり、それらがどのように相互作用するかを理解することは、プログラムを作成する上で非常に重要です。オブジェクト指向の概念について学びたい場合は、「Objective-Cを使用したオブジェクト指向プログラミング」を参照してください。

Objective-C:Cのスーパーセット

Objective-CはC言語の厳密なスーパーセットです。つまり、C言語のプログラムは修正なしでそのままObjective-Cコンパイラを通過でき、Objective-CでC言語コードを使用することも完全に合法です。Objective-CはC言語の上に載った薄い層と表現されます。なぜなら、Objective-Cの本来の意図はC言語の本体にオブジェクト指向の機能を追加することだからです。

Objective-Cコードのファイル拡張子

拡張子 内容タイプ
.h ヘッダファイル。ヘッダファイルには、クラス、型、関数、定数の宣言が含まれます。
.m ソースコードファイル。これは典型的なソースコードファイルの拡張子で、Objective-CコードとCコードを含めることができます。
.mm ソースコードファイル。この拡張子を持つソースコードファイルには、Objective-CコードとCコードに加えて、C++コードを含めることができます。この拡張子は、Objective-Cコードで実際にC++クラスまたは機能を使用する必要がある場合にのみ使用してください。

ソースコードにヘッダファイルを含める必要がある場合、標準の #include コンパイラオプションを使用できますが、Objective-Cにはより良い方法があります。#import オプションは #include オプションとまったく同じですが、同じファイルが一度だけ含まれることを保証します。Objective-Cの例とドキュメントは #import を使用する傾向があるため、あなたのコードもそうすべきです。

構文

Objective-Cのオブジェクト指向構文は、Smalltalkのメッセージ送信スタイルに由来します。変数型、プリプロセッサ、フロー制御、関数の宣言と呼び出しなど、他のすべての非オブジェクト指向構文はC言語と完全に同じです。ただし、C言語では合法な構文コードでも、Objective-Cでは意味が異なる場合があります。たとえば、一部のブール式では、C言語では戻り値がtrueですが、Objective-Cでyesと直接比較すると関数はエラーになります。Objective-Cではyesの値は1しか表さないためです。

最初のObjective-Cプログラム、Xcode 4.3.1ベース:

#import <Foundation/Foundation.h>

int main(int argc, char *argv[]) {

    @autoreleasepool {
        NSLog(@"Hello World!");
    }

   return 0;
}

メッセージ送信

Objective-Cの最大の特徴は、Smalltalkから受け継いだメッセージ送信モデルであり、このメカニズムは現在のC++スタイルの主流とは大きく異なります。Objective-Cでは、オブジェクトが互いにメソッドを呼び出すというよりも、オブジェクト間でメッセージを送受信するという方が正確です。この2つのスタイルの主な違いは、メソッドの呼び出し/メッセージ送信という動作にあります。C++では、クラスとメソッドの関係は厳密に明確であり、メソッドは必ずクラスに属し、コンパイル時に緊密にバインドされているため、存在しないクラスのメソッドを呼び出すことはできません。しかしObjective-Cでは、クラスとメッセージの関係は緩やかで、メソッド呼び出しはオブジェクトにメッセージを送信することと見なされ、すべてのメソッドはメッセージへの応答と見なされます。すべてのメッセージ処理は実行時に動的に決定され、クラスが受信したメッセージをどのように処理するかを独自に決定します。つまり、クラスが受信したメッセージに必ず応答するとは限らず、クラスが処理できないメッセージを受信した場合、プログラムは例外をスローするだけで、エラーやクラッシュは発生しません。

C++では、オブジェクトにメッセージを送信する(つまりメソッドを呼び出す)構文は次のとおりです:

obj.method(argument);

Objective-Cでは次のように書きます:

[obj method: argument];

この2つは構文上の違いだけでなく、基本的な動作にも違いがあります。

ここでは、自動車クラス(car class)の簡単な例を使用して、Objective-Cのメッセージ送信機能を説明します:

[car fly];

典型的なC++の解釈は「carクラスのflyメソッドを呼び出す」です。carクラスにflyメソッドが定義されていない場合、コンパイルは確実に失敗します。しかしObjective-Cでは、「carオブジェクトにflyメッセージを送信する」と解釈すべきです。flyはメッセージであり、carはメッセージの受信者です。carはメッセージを受信した後、そのメッセージにどのように応答するかを決定します。carクラスにflyメソッドが定義されている場合はそのメソッドのコードを実行し、car内にflyメソッドが存在しない場合でもプログラムはコンパイルを通過できますが、実行時に例外がスローされます。

この2つのスタイルにはそれぞれ長所と短所があります。C++はすべてのメソッドに対応する動作が必要であり、コンパイル時のバインディングにより関数呼び出しが非常に高速です。欠点は、virtualキーワードによってのみ限定的な動的バインディングを提供できることです。Objective-Cは生まれつきダックタイピングの動的バインディング能力を備えています。実行時にメッセージを処理するため、未知のメッセージをオブジェクトに送信できます。オブジェクトのコレクション全体に、各オブジェクトの型を1つずつチェックすることなくメッセージを送信でき、メッセージ転送メカニズムも備えています。また、空のオブジェクトnilはメッセージを受信してもデフォルトでは何もしないため、nilにメッセージを送信してもプログラムがクラッシュする心配はありません。

文字列

C言語のスーパーセットとして、Objective-CはC言語の文字列に関する規則をサポートしています。つまり、単一の文字は一重引用符で囲み、文字列は二重引用符で囲みます。しかし、多くのObjective-Cコードでは通常、C言語スタイルの文字列を使用しません。代わりに、多くのフレームワークは文字列をNSStringオブジェクトに渡します。NSStringクラスは、文字列のクラスラッパーを提供し、任意の長さの文字列を保存するための組み込みメモリ管理メカニズム、Unicodeのサポート、printfスタイルのフォーマットツールなど、期待されるすべての利点を含みます。このような文字列は非常に頻繁に使用されるため、Objective-Cには定数値からNSStringオブジェクトを簡単に作成できるショートカットが用意されています。このショートカットを使用するには、通常の二重引用符で囲まれた文字列の前に@記号を置くだけです。次の例のようになります:

NSString* myString = @"My String\n";
NSString* anotherString = [NSString stringWithFormat:@"%d %s", 1, @"String"];

// 从一个C语言字符串创建Objective-C字符串
NSString*  fromCString = [NSString stringWithCString:"A C string" 
encoding:NSASCIIStringEncoding];

类

他のすべてのオブジェクト指向言語と同様に、クラスはObjective-Cがデータをカプセル化し、データを操作する動作をカプセル化するための基本構造です。オブジェクトはクラスの実行時インスタンスであり、クラスで宣言されたインスタンス変数のメモリコピーと、クラスメンバーへのポインタを含みます。Objective-Cのクラス仕様は、定義(interface)と実装(implementation)の2つの部分で構成されています。定義(interface)部分には、クラス宣言とインスタンス変数の定義、およびクラスに関連するメソッドが含まれます。実装(implementation)部分には、クラスメソッドの実際のコードが含まれます。

次の図は、NSObject基底クラスを継承するMyClassというクラスを宣言する構文を示しています。クラス宣言は常に@interfaceコンパイラオプションで始まり、@endコンパイラオプションで終わります。クラス名の後(コロンで区切られた)には親クラスの名前があります。クラスのインスタンス(またはメンバー)変数は、中括弧で囲まれたコードブロック内で宣言されます。インスタンス変数ブロックの後には、クラス宣言のメソッドのリストがあります。各インスタンス変数とメソッド宣言はセミコロンで終わります。

クラスの定義ファイルはC言語の慣例に従い、拡張子は.h、実装ファイルは.mです。

クラス宣言図

Interface

定義部分は、クラスの名前、データメンバー、メソッドを明確に定義します。キーワード@interfaceで始まり、@endで終わります。

@interface MyObject : NSObject {
    int memberVar1; // 实体变量
    id  memberVar2;
}

+(return_type) class_method; // 类方法

-(return_type) instance_method1; // 实例方法
-(return_type) instance_method2: (int) p1;
-(return_type) instance_method3: (int) p1 andPar: (int) p2;
@end

メソッドの前にある +/- 記号は関数のタイプを表します。プラス記号(+)はクラスメソッドを表し、インスタンスなしで呼び出すことができ、C++の静的メンバー関数(static member function)に似ています。マイナス記号(-)は一般的なインスタンスメソッドです。

ここに、意味が似ているC++構文の対照表を示します:

class MyObject : public NSObject {
protected:
    int memberVar1;  // 实体变量
    void * memberVar2;

  public:
    static return_type class_method(); // 類方法

    return_type instance_method1();    // 实例方法
    return_type instance_method2( int p1 );
    return_type instance_method3( int p1, int p2 );
}

Objective-Cで新しいメソッドを定義するとき、名前の中のコロン(:)は引数の受け渡しを表します。C言語が数学関数のように括弧で引数を渡すのとは異なります。Objective-Cのメソッドでは、引数を名前の途中に挟むことができるため、すべてをメソッド名の末尾に付ける必要がなく、プログラムの可読性が向上します。色のRGB値を設定するメソッドを例に挙げます:

- (void) setColorToRed: (float)red Green: (float)green Blue:(float)blue; /* 宣告方法*/

[myColor setColorToRed: 1.0 Green: 0.8 Blue: 0.2]; /* 呼叫方法*/

このメソッドのシグネチャは setColorToRed:Green:Blue: です。各コロンの後には float 型のパラメータが1つずつ続き、それぞれ赤、緑、青の3色を表します。

Implementation

実装ブロックには公開メソッドの実装と、プライベート(private)変数およびメソッドの定義が含まれます。ブロックはキーワード @implementation で始まり、@end で終わります。

@implementation MyObject {
  int memberVar3; //私有實體變數
}

+(return_type) class_method {
    .... //method implementation
}
-(return_type) instance_method1 {
     ....
}
-(return_type) instance_method2: (int) p1 {
    ....
}
-(return_type) instance_method3: (int) p1 andPar: (int) p2 {
    ....
}
@end

特筆すべき点として、Interfaceブロックだけでなく Implementation ブロックでもインスタンス変数を定義できます。両者の違いはアクセス権限にあります。Interfaceブロック内のインスタンス変数のデフォルト権限は protected であり、implementation ブロックで宣言されたインスタンス変数はデフォルトで private です。したがって、Implementationブロックでプライベートメンバーを定義するほうがオブジェクト指向のカプセル化の原則に合致します。なぜなら、そのようにすればクラスのプライベート情報を公開インターフェース(.hファイル)に晒す必要がないからです。

オブジェクトの作成

Objective-C でオブジェクトを生成するには、alloc と init という2つのメッセージを送る必要があります。alloc はメモリを確保する役割を持ち、init はオブジェクトを初期化します。init と alloc はどちらも NSObject に定義されているメソッドであり、親オブジェクトがこれら2つのメッセージを受け取って正しく応答した後に、新しいオブジェクトの生成が完了します。以下に例を示します。

MyObject * my = [[MyObject alloc] init];

Objective-C 2.0 では、オブジェクト生成時にパラメータが不要な場合は new を直接使用できます。

MyObject * my = [MyObject new];

これは単に構文を簡潔にしただけで、効果は完全に同じです。

初期化プロセスを独自に定義したい場合は、init メソッドをオーバーライドして追加の処理を組み込むことができます。(用途は C++ のコンストラクター(constructor)に似ています)

メソッド

Objective-C のクラスは、インスタンスメソッドとクラスメソッドの2種類のメソッドを宣言できます。インスタンスメソッドとは、クラスの特定のインスタンスのスコープ内で実行されるメソッドです。つまり、インスタンスメソッドを呼び出す前に、まずクラスのインスタンスを作成する必要があります。一方、クラスメソッドは、インスタンスを作成する必要がありません。

メソッド宣言は、メソッドタイプ識別子、戻り値の型、1つ以上のメソッド識別キーワード、パラメータの型と名前の情報で構成されます。下の図は insertObject:atIndex: インスタンスメソッドの宣言を示しています。宣言はマイナス記号(-)で始まり、これがインスタンスメソッドであることを示します。メソッドの実際の名前(insertObject:atIndex:)は、すべてのメソッド識別キーワードを連結したもので、コロンが含まれます。コロンはパラメータの存在を示します。メソッドにパラメータがない場合、最初の(そして唯一の)メソッド識別キーワードの後のコロンを省略できます。この例では、このメソッドには2つのパラメータがあります。

メソッド宣言構文

メソッドを呼び出したいときは、対応するオブジェクトにメッセージを送信します。ここでのメッセージとは、メソッド識別子と、メソッドに渡すパラメータ情報です。オブジェクトに送信されたすべてのメッセージは動的にディスパッチされます。これにより、Objective-C クラスのポリモーフィズム(多態性)の動作を実現しやすくなります。つまり、サブクラスがスーパークラスと同じ識別子を持つメソッドを定義している場合、サブクラスが最初にメッセージを受け取り、その後、メッセージをスーパークラスに転送するかどうかを選択できます(転送しないこともできます)。

メッセージは角括弧([ と ])で囲まれます。角括弧の中では、メッセージを受け取るオブジェクトが左側にあり、メッセージ(メッセージが必要とする任意のパラメータを含む)が右側にあります。例えば、myArray 変数に insertObject:atIndex: メッセージを送信するには、次のような構文を使用する必要があります。

[myArray insertObject:anObj atIndex:0];

一時的な結果を保持するためにローカル変数を過剰に宣言するのを避けるため、Objective-C ではネストしたメッセージを使用できます。各ネストメッセージの戻り値は、他のメッセージのパラメータやターゲットとして使用できます。例えば、前述の例の任意の変数の代わりに、そのような値を取得する任意のメッセージを使用できます。つまり、myAppObject という別のオブジェクトが、配列オブジェクトにアクセスするメソッドと、オブジェクトを配列に挿入するメソッドを持っている場合、前述の例を次のように書くことができます。

[[myAppObject getArray] insertObject:[myAppObject getObjectToInsert] atIndex:0];

前述の例はすべてクラスのインスタンスにメッセージを送信するものでしたが、クラス自体にメッセージを送信することもできます。クラスにメッセージを送信する場合、指定するメソッドはインスタンスメソッドではなくクラスメソッドとして定義されている必要があります。クラスメソッドは C++ のクラスにおける静的メンバーに少し似ていると考えて構いません(ただし完全に同じではありません)。

クラスメソッドの典型的な用途は、新しいクラスインスタンスを作成するファクトリメソッドとして使用することや、クラスに関連する共有情報にアクセスする手段として使用することです。クラスメソッドの宣言構文はインスタンスメソッドとほとんど完全に同じですが、一点だけ小さな違いがあります。インスタンスメソッドがメソッドタイプ識別子としてマイナス記号を使用するのに対し、クラスメソッドはプラス記号(+)を使用します。

次の例は、クラスメソッドがクラスのファクトリメソッドとしてどのように機能するかを示しています。ここでは、arrayWithCapacity は NSMutableArray クラスのクラスメソッドであり、クラスの新しいインスタンスのために領域を確保して初期化し、それを返します。

NSMutableArray*   myArray = nil; // nil 基本上等同于 NULL

// 创建一个新的数组,并把它赋值给 myArray 变量
myArray = [NSMutableArray arrayWithCapacity:0];

プロパティ

プロパティは、アクセッサメソッドの宣言の代わりとなる便利な方法です。プロパティはクラス宣言内に新しいインスタンス変数を作成しません。それらは、既存のインスタンス変数にアクセスするメソッドを定義するための省略記法にすぎません。インスタンス変数を公開するクラスは、getter や setter の構文の代わりにプロパティ表記を使用できます。また、クラスはプロパティを使用して「仮想」のインスタンス変数を公開することもできます。これは、実際にインスタンス変数内に保存されているのではなく、一部のデータを動的に計算した結果です。

実際のところ、プロパティは大量の冗長なコードを書く手間を省いてくれます。ほとんどのアクセッサメソッドは同様の方法で実装されるため、プロパティによって、クラスが公開する各インスタンス変数に対して個別の getter や setter を提供する必要がなくなります。その代わりに、プロパティ宣言で希望する動作を指定し、コンパイル時にその宣言に基づいて実際の getter および setter メソッドを合成します。

プロパティ宣言は、クラスインターフェースのメソッド宣言の場所に置くべきです。基本的な定義では @property コンパイラディレクティブを使用し、その直後に型情報とプロパティ名を続けます。また、カスタムオプションを使用してプロパティを設定することもでき、これによりアクセッサメソッドの動作が決まります。次の例は、いくつかの単純なプロパティ宣言を示しています。

@interface Person : NSObject {
    @public
        NSString *name;
    @private
        int age;
}

@property(copy) NSString *name;
@property(readonly) int age;

-(id)initWithAge:(int)age;
@end

プロパティのアクセサメソッドは @synthesize キーワードによって実装され、プロパティ宣言から自動的に一対のアクセサメソッドが生成されます。また、@dynamic キーワードを使用して、アクセサメソッドがプログラマによって手動で提供されることを示すこともできます。

@implementation Person
@synthesize name;
@dynamic age;

-(id)initWithAge:(int)initAge
{
    age = initAge; // 注意:直接赋给成员变量,而非属性
    return self;
}

-(int)age
{
    return 29; // 注意:并非返回真正的年龄
}
@end

プロパティは、従来のメッセージ式、ドット式、または "valueForKey:" / "setValue:forKey:" メソッドのペアを使用してアクセスできます。

Person *aPerson = [[Person alloc] initWithAge: 53];
aPerson.name = @"Steve"; // 注意:点表达式,等于[aPerson setName: @"Steve"];
NSLog(@"Access by message (%@), dot notation(%@), property name(%@) and direct instance variable access (%@)",
      [aPerson name], aPerson.name, [aPerson valueForKey:@"name"], aPerson->name);

ドット式を使用してインスタンスのプロパティにアクセスするには、"self" キーワードを使用する必要があります:

-(void) introduceMyselfWithProperties:(BOOL)useGetter
{
    NSLog(@"Hi, my name is %@.", (useGetter ? self.name : name)); // NOTE: getter vs. ivar access
}

クラスまたはプロトコルのプロパティは動的に読み取ることができます。

int i;
int propertyCount = 0;
objc_property_t *propertyList = class_copyPropertyList([aPerson class], &propertyCount);

for ( i=0; i < propertyCount; i++ ) {
    objc_property_t *thisProperty = propertyList + i;
    const char* propertyName = property_getName(*thisProperty);
    NSLog(@"Person has a property: '%s'", propertyName);
}

高速列挙

NSEnumerator オブジェクトを使用したり、コレクション内を順に列挙したりする代わりに、Objective-C 2.0 は高速列挙の構文を提供します。Objective-C 2.0 では、以下のループは機能的に同等ですが、パフォーマンス特性が異なります。

// 使用NSEnumerator
NSEnumerator *enumerator = [thePeople objectEnumerator];
Person *p;

while ( (p = [enumerator nextObject]) != nil ) {
    NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用依次枚举
for ( int i = 0; i < [thePeople count]; i++ ) {
    Person *p = [thePeople objectAtIndex:i];
    NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用快速枚举
for (Person *p in thePeople) {
    NSLog(@"%@ is %i years old.", [p name], [p age]);
}

高速列挙は標準の列挙よりも効率的なコードを生成できます。列挙が呼び出していたメソッドが、NSFastEnumeration プロトコルが提供するポインタ演算に置き換えられるためです。


プロトコル

プロトコルは、実装を持たないメソッドのリストです。任意のクラスがプロトコルを採用し、そのメソッド群を具体的に実装できます。

Objective-C は NeXT 時代に多重継承の概念を導入しようと試みましたが、プロトコルの登場により実装されませんでした。

プロトコルは Java や C# の「インターフェース」に似ています。Objective-C にはプロトコルを定義する2つの方法があります。コンパイラによって保証される「正式プロトコル」と、特定の目的のために設定される「非正式プロトコル」です。

非正式プロトコル非正式プロトコルは、選択的に実装できる一連のメソッドリストです。非正式プロトコルはプロトコルという名前ですが、実際には NSObject にぶら下がった未実装カテゴリ(Unimplemented Category)の一種を指す呼称であり、Objective-C の言語機構として非正式プロトコルというものは存在しません。OSX 10.6 以降では @optional キーワードが導入されたことにより、正式プロトコルが同じ能力を持つようになったため、非正式プロトコルは廃止され、使用されなくなりました。

正式プロトコルJava の「インターフェース」に似ており、一連のメソッドのリストです。任意のクラスは自身が特定のプロトコルを実装していると宣言できます。Objective-C 2.0 より前では、クラスは宣言に適合するプロトコルのすべてのメソッドを実装しなければならず、そうでない場合、コンパイラはそのクラスが宣言に適合するプロトコルのすべてのメソッドを実装していないことを示すエラーを報告しました。Objective-C 2.0 では、プロトコル内の一部のメソッドをオプション(Optional)としてマークできるようになり、コンパイラはこれらのオプションメソッドの実装を強制しなくなりました。

プロトコルは、Cocoa におけるデリゲート(委譲)やイベント発生にしばしば使用されます。例えば、テキストフィールドクラスには通常デリゲート(delegate)オブジェクトが含まれており、そのオブジェクトはプロトコルを実装できます。そのプロトコルには、テキスト入力の自動補完を実装するメソッドが含まれることがあります。このデリゲートオブジェクトがそのメソッドを実装している場合、テキストフィールドクラスは適切なタイミングで自動補完イベントを発生させ、自動補完機能のためにこのメソッドを呼び出します。

Objective-C のプロトコルの概念は Java のインターフェースの概念と完全に同じではありません。つまり、あるクラスは特定のプロトコルに適合することを宣言しなくても、そのプロトコルに含まれるメソッドを実装でき、実質的にそのプロトコルに適合することになります。そして、この違いは外部コードからは見えません。正式プロトコルの宣言は実装を提供せず、単にそのプロトコルに適合するクラスがそのプロトコルのメソッドを実装していることを示し、呼び出し側が安全にメソッドを呼び出せることを保証します。

構文

プロトコルはキーワード @protocol でブロックを開始し、@end で終了し、その間にメソッドリストを置きます。

@protocol Locking
- (void)lock;
- (void)unlock;
@end

これはプロトコルの例です。マルチスレッドプログラミングでは、共有リソースを同時に1つのスレッドだけが使用できるようにするため、使用前にそのリソースにロックをかけることがよくあります。上記は「ロック」という概念を持つことを示すプロトコルであり、プロトコルには2つのメソッドがありますが、名前だけでまだ実装されていません。

次の SomeClass は、Locking プロトコルを採用していると宣言しています:

@interface SomeClass : SomeSuperClass <Locking>
@end

SomeClass が Locking プロトコルを採用していると宣言した時点で、SomeClass は Locking プロトコル内の2つのメソッドを実装する義務を負います。

@implementation SomeClass
- (void)lock {
  // 實現lock方法...
}
- (void)unlock {
  // 實現unlock方法...
}
@end

SomeClass が確かに Locking プロトコルに準拠しているため、呼び出し側は SomeClass オブジェクト変数に対して lock または unlock メッセージを安全に送信でき、メッセージに応答できないことを心配する必要はありません。

プラグインは抽象定義を使用するもう一つの例であり、プラグインの実装を気にせずに、望ましい動作を定義することができます。


動的型付け

Smalltalk と同様に、Objective-C は動的型付けを備えています。つまり、オブジェクトの公開インターフェースに対応するメソッドがあるかどうかに関わらず、任意のオブジェクトにメッセージを送信できます。C++ のような静的型付け言語と比較すると、コンパイラは (void*) ポインタに対するメソッド呼び出しを拒否します。しかし Objective-C では、id に対して任意のメッセージを送信できます(id は void* によく似ていますが、オブジェクト専用に厳しく制限されています)。コンパイラは「そのオブジェクトはメッセージに応答できない可能性があります」という警告を出すだけで、プログラムはコンパイルを通過できます。実際に起こることは実行時のオブジェクトの実際の形態に依存し、そのオブジェクトが確かにメッセージに応答できる場合、対応するメソッドが実行されます。

オブジェクトがメッセージを受け取った後、メッセージを処理するための可能な手段は3つあります。1つ目はメッセージに応答してメソッドを実行することです。応答できない場合は、メッセージを他のオブジェクトに転送できます。上記のいずれもできない場合は、応答できないために送出された例外を処理しなければなりません。この3つのいずれかを実行すれば、そのメッセージはタスクを完了したものとして破棄されます。「nil」(空のオブジェクトポインタ)にメッセージを送信した場合、そのメッセージは通常無視されますが、コンパイラオプションによっては例外が送出されることがあります。

Objective-C は動的型付けの能力を備えていますが、コンパイル時の静的型チェックは依然として変数に適用できます。次の3つの宣言は実行時の効果は完全に同じですが、それぞれがより明確な型情報を提供します。追加の型情報により、コンパイラはコンパイル時に変数の型をチェックし、型が一致しない変数に対して警告を出すことができます。

以下の3つのメソッドは、パラメータの型のみが異なります:

- setMyValue:(id) foo;

id 型は、パラメータ「foo」が任意のクラスのインスタンスであり得ることを示します。

- setMyValue:(id <aProtocol>) foo;

id<aProtocol> は、「foo」が任意のクラスのインスタンスであり得るが、「aProtocol」プロトコルに準拠している必要があることを示します。

- setMyValue:(NSNumber*) foo;

この宣言は、「foo」が「NSNumber」のインスタンスでなければならないことを示します。

動的型付けは強力な特性です。ジェネリクスがない静的型付け言語(Java 5 より前のバージョンなど)でコンテナクラスを実装する場合、プログラマは汎用オブジェクト型を対象としたコンテナクラスを書き、汎用型と実際の型の間で絶えず型キャストを行う必要があります。いずれにせよ、型キャストは静的型安全性を壊します。例えば、「整数」を書き込んでそれを「文字列」として読み出すと、実行時エラーが発生します。このような問題はジェネリクスによって解決されましたが、コンテナクラスは内容オブジェクトの型が一致している必要があります。一方、動的型付け言語ではこのような問題はまったくありません。


フォワーディング

Objective-C では、オブジェクトがメッセージに応答できるかどうかに関係なく、オブジェクトにメッセージを送信できます。メッセージに応答するか破棄する以外に、オブジェクトはメッセージを応答できるオブジェクトに転送することもできます。フォワーディングは、オブザーバーパターンやプロキシパターンなどの特定のデザインパターンを簡素化するために使用できます。

Objective-C ランタイムは、Object 内で一対のメソッドを定義しています:

フォワーディングメソッド:

- (retval_t) forward:(SEL) sel :(arglist_t) args; // with GCC
- (id) forward:(SEL) sel :(marg_list) args; // with NeXT/Apple systems

レスポンスメソッド:

- (retval_t) performv:(SEL) sel :(arglist_t) args;  // with GCC
- (id) performv:(SEL) sel :(marg_list) args; // with NeXT/Apple systems

フォワーディングを実装したいオブジェクトは、新しいメソッドで上記のメソッドをオーバーライドして、フォワーディング動作を定義するだけです。応答メソッド performv:: を書き換える必要はありません。そのメソッドは単に応答オブジェクトにメッセージを送信し、パラメータを渡すだけだからです。ここで、SEL 型は Objective-C におけるメッセージの型です。

次のコードは、フォワーディングの基本概念を示しています:

Forwarder.h ファイルのコード:

#import <objc/Object.h>

@interface Forwarder : Object
{
    id recipient; //该对象是我们希望转发到的对象。
}

@property (assign, nonatomic) id recipient;

@end

Forwarder.m ファイルのコード:

#import "Forwarder.h"

@implementation Forwarder

@synthesize recipient;

- (retval_t) forward: (SEL) sel : (arglist_t) args
{
    /*
     *检查转发对象是否响应该消息。
     *若转发对象不响应该消息,则不会转发,而产生一个错误。
     */
    if([recipient respondsTo:sel])
       return [recipient performv: sel : args];
    else
       return [self error:"Recipient does not respond"];
}

Recipient.h ファイルのコード:

#import <objc/Object.h>

// A simple Recipient object.
@interface Recipient : Object
- (id) hello;
@end

Recipient.m ファイルのコード:

#import "Recipient.h"

@implementation Recipient

- (id) hello
{
    printf("Recipient says hello!\n");

    return self;
}

@end

main.m ファイルのコード:

#import "Forwarder.h"
#import "Recipient.h"

int main(void)
{
    Forwarder *forwarder = [Forwarder new];
    Recipient *recipient = [Recipient new];

    forwarder.recipient = recipient; //Set the recipient.
    /*
     *转发者不响应hello消息!该消息将被转发到转发对象。
     *(若转发对象响应该消息)
     */
    [forwarder hello];

    return 0;
}

GCC でコンパイルすると、コンパイラは次のように報告します:

$ gcc -x objective-c -Wno-import Forwarder.m Recipient.m main.m -lobjc
main.m: In function `main':
main.m:12: warning: `Forwarder' does not respond to `hello'
$

前述したように、コンパイラは Forwarder クラスが hello メッセージに応答しないと報告します。この場合、フォワーディングが実装されているため、この警告は無視できます。このプログラムを実行すると、次のような出力が生成されます:

$ ./a.out
Recipient says hello!

カテゴリ

Objective-C の設計における主な関心事の1つは、大規模なコードフレームワークの保守です。構造化プログラミングの経験から、コードを改善する主要な方法の1つは、コードをより小さな断片に分割することであることが示されています。Objective-C は、Smalltalk 実装における「カテゴリ」の概念を借用して拡張し、コードを分割する目的を達成するのに役立てています。

カテゴリは、メソッドの実装を一連の分離されたファイルに分割できます。プログラマは関連するメソッドのグループを1つのカテゴリに入れることで、プログラムをより読みやすくできます。例えば、文字列クラスに「スペルチェック」という名前のカテゴリを追加し、スペルチェックに関連するコードをそのカテゴリに入れることができます。

さらに、カテゴリ内のメソッドは実行時にクラスに追加されます。この特性により、プログラマは既存のコードを保有したり、既存のクラスを再コンパイルしたりすることなく、既存のクラスにメソッドを追加できます。例えば、システム提供の文字列クラスの実装にスペルチェック機能が含まれていない場合、元の文字列クラスのコードを変更せずにその機能を追加できます。

実行時には、カテゴリ内のメソッドはクラスの元のメソッドと何ら変わりはなく、そのコードはプライベートなクラスメンバ変数を含むすべてのメンバ変数にアクセスできます。

カテゴリがクラス内の既存メソッドと同じ名前の関数を宣言した場合、カテゴリ内のメソッドが呼び出されます。したがって、カテゴリはクラスにメソッドを追加できるだけでなく、既存のメソッドを置き換えることもできます。この特性は、既存コードのバグを修正するために使用でき、プログラム内の既存クラスの動作を根本的に変更することもできます。2つのカテゴリのメソッドが同じ名前の場合、呼び出されるメソッドは予測できません。

他の言語も、さまざまな方法でこの言語機能を追加しようと試みました。TOM はこの点でさらに進んでおり、メソッドの追加だけでなくメンバ変数の追加も許可しています。また、宣言指向のソリューションを使用する言語もありますが、その中で最も注目に値するのは Self 言語です。

C# と Visual Basic.NET は、拡張メソッドと部分クラスの方法で同様の機能を実装しています。Ruby と一部の動的言語は、この技術を「モンキーパッチ」という名前で呼んでいます。

カテゴリを使用する例

この例では、Integer クラスを作成します。このクラス自体は integer プロパティのみを定義しており、その後、クラスの機能を拡張するために Arithmetic と Display の2つのカテゴリを追加します。カテゴリはクラスのプライベートメンバにアクセスできますが、通常はプロパティのアクセサメソッドを使用してアクセスする方が良い方法であり、カテゴリを元のクラスからより独立させることができます。これはカテゴリの典型的な応用です。もう1つの応用は、カテゴリを使用して既存クラスのメソッドを置き換えることですが、継承ではなくカテゴリを使用してメソッドを置き換えることは良い方法とは見なされていません。

Integer.h ファイルのコード:

#import <objc/Object.h>

@interface Integer : Object
{
@private
    int integer;
}

@property (assign, nonatomic) integer;

@end

Integer.m ファイルのコード:

#import "Integer.h"

@implementation Integer

@synthesize integer;

@end

Arithmetic.h ファイルのコード:

#import "Integer.h"

@interface Integer(Arithmetic)
- (id) add: (Integer *) addend;
- (id) sub: (Integer *) subtrahend;
@end

Arithmetic.m ファイルのコード:

#import "Arithmetic.h"

@implementation Integer(Arithmetic)
- (id) add: (Integer *) addend
{
    self.integer = self.integer + addend.integer;
    return self;
}

- (id) sub: (Integer *) subtrahend
{
    self.integer = self.integer - subtrahend.integer;
    return self;
}
@end

Display.h ファイルのコード:

#import "Integer.h"

@interface Integer(Display)
- (id) showstars;
- (id) showint;
@end

Display.m ファイルのコード:

#import "Display.h"

@implementation Integer(Display)
- (id) showstars
{
    int i, x = self.integer;
    for(i=0; i < x; i++)
       printf("*");
    printf("\n");

    return self;
}

- (id) showint
{
    printf("%d\n", self.integer);

    return self;
}
@end

main.m ファイルのコード:

#import "Integer.h"
#import "Arithmetic.h"
#import "Display.h"

int
main(void)
{
    Integer *num1 = [Integer new], *num2 = [Integer new];
    int x;

    printf("Enter an integer: ");
    scanf("%d", &x);

    num1.integer = x;
    [num1 showstars];

    printf("Enter an integer: ");
    scanf("%d", &x);

    num2.integer = x;
    [num2 showstars];

    [num1 add:num2];
    [num1 showint];

    return 0;
}

次のコマンドを使用してコンパイルします:

gcc -x objective-c main.m Integer.m Arithmetic.m Display.m -lobjc

コンパイル時には、#import "Arithmetic.h" と [num1 add:num2] コマンド、および Arithmetic.m ファイルを省略して実験することができます。プログラムは依然として実行できます。これは、カテゴリを動的に、必要に応じてロードできることを示しています。特定のカテゴリが提供する機能が不要な場合は、それを単にコンパイルしないことができます。


ガベージコレクション

Objective-C 2.0 は、オプションのガベージコレクタを提供します。後方互換モードでは、Objective-C ランタイムは「retain」や「release」などの参照カウント操作をノーオペレーションにします。ガベージコレクションが有効な場合、すべてのオブジェクトがコレクタの収集対象となります。通常の C ポインタは「__strong」で修飾でき、ポインタが指すオブジェクトがまだ使用中であることを示します。「__weak」とマークされたポインタはコレクタのカウントに含まれず、オブジェクトが回収されると「nil」に書き換えられます。iOS 上の Objective-C 2.0 実装にはガベージコレクタは含まれていません。ガベージコレクタは低優先度のバックグラウンドスレッドで実行され、ユーザー操作時に一時停止できるため、良好なユーザーエクスペリエンスを維持できます。

参考リンク:https://zh.wikipedia.org/wiki/Objective-C