2005年01月11日
オブジェクト指向言語は、更に状態と操作を区別している事を隠蔽し、1つの単位とします。 またプログラム言語はデータと関数間のやりとりは完全に切り離す事ができないため、構造化言語は特に固有のデータ構造への依存度が高いです。これにより再利用性の難しさが上げられます。
Objective-Cは、C++,Javaの静的型定義ではなく、Smalltalkと同様に動的型定義です。これはクラス定義を実行時まで引き伸ばします。 そのため、動的バインディングが可能になっています。動的バインディングとは、呼び出すメソッドを実行時に決定する事です。C++,Javaでも 似たような機能を実装できますが、コンパイル時にある程度の型が確定しているため、その型に依存する事になります。このようなバインディングを 遅延バインディングと呼びます。動的ロードも可能です。これはプログラム実行中にプログラムの追加が可能なことを意味します。
gcc (GCC) 3.2 20020903 (Red Hat Linux 8.0 3.2-7)
上記のようにgcc3.2では、gccでObjective-Cのソースコードをコンパイルするためにgcc-objcパッケージとランタイムのlibobjcが必要になります。
gcc-objc - Objective-C support for GCC
libobjc - Objective-C runtimeapt-getがあれば、インストールが楽なのでapt-getを入手してインストールしてください。
[root@stoc root]# apt-get install gcc-objc
ライブラリの確認をします。
[root@stoc root]# ls -l /usr/lib/libobjc* lrwxrwxrwx 1 root root 16 Nov 3 12:59 /usr/lib/libobjc.so.1 -> libobjc.so.1.0.0 -rwxr-xr-x 1 root root 107731 Sep 4 2002 /usr/lib/libobjc.so.1.0.0
真の意味でのオブジェクト指向を追及するためSmalltalk,Simulaを学習するための布石とします。またいつものとおり2CバイナリクロックのObjective-C版を作成します。
// 一行のコメント
/* 複数行のコメント */
Apple社のコーディング規約(Coding Guidelines)が参考になります。
ヘッダファイルにインタフェースを書きます。これはC言語のヘッダと同様で実装を隠蔽します。
[s-okita@stoc objc]$ cat hello.h #import <stdio.h> #import <objc/Object.h> @interface TestClass : Object - (void) getMessage; @end
詳細な実装は、.mファイルに記述します。
[s-okita@stoc objc]$ cat hello.m #import "hello.h" @implementation TestClass - (void) getMessage { printf("Hello Objective-C World\n"); } @end int main(int argc, char *argv[]) { id obj = [ TestClass alloc ]; [ obj getMessage ]; return 0; }コンパイル。-vオプションをつけてプリプロセス、コンパイル、アセンブル、リンクを表示させています。。objective-cをコンパイル(リンケージ)するには、-lobjcが必要です。またヘッダファイルは以下の場所にあります。
[s-okita@stoc objc]$ gcc -v -Wall -O2 -o hello hello.m -lobjc Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/3.2/specs Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --disable-checking --host=i386-redhat-linux --with-system-zlib --enable-__cxa_atexit Thread model: posix gcc version 3.2 20020903 (Red Hat Linux 8.0 3.2-7) /usr/lib/gcc-lib/i386-redhat-linux/3.2/cc1obj -v -D__GNUC__=3 -D__GNUC_MINOR__=2 -D__GNUC_PATCHLEVEL__=0 -D__GXX_ABI_VERSION=102 -D__ELF__ -Dunix -D__gnu_linux__ -Dlinux -D__ELF__ -D__unix__ -D__gnu_linux__ -D__linux__ -D__unix -D__linux -Asystem=posix -D__OPTIMIZE__ -D__STDC_HOSTED__=1 -Acpu=i386 -Amachine=i386 -Di386 -D__i386 -D__i386__ -D__tune_i386__ hello.m -quiet -dumpbase hello.m -O2 -Wall -version -o /tmp/cc4yU6vh.s GNU CPP version 3.2 20020903 (Red Hat Linux 8.0 3.2-7) (cpplib) (i386 Linux/ELF) GNU Objective-C version 3.2 20020903 (Red Hat Linux 8.0 3.2-7) (i386-redhat-linux) compiled by GNU C version 3.2 20020903 (Red Hat Linux 8.0 3.2-7). ignoring nonexistent directory "/usr/i386-redhat-linux/include" #include "..." search starts here: #include <...> search starts here: /usr/local/include /usr/lib/gcc-lib/i386-redhat-linux/3.2/include /usr/include End of search list. hello.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file as -V -Qy -o /tmp/ccA7Zevv.o /tmp/cc4yU6vh.s GNU assembler version 2.13.90.0.2 (i386-redhat-linux) using BFD version 2.13.90.0.2 20020802 /usr/lib/gcc-lib/i386-redhat-linux/3.2/collect2 --eh-frame-hdr -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o hello /usr/lib/gcc-lib/i386-redhat-linux/3.2/../../../crt1.o /usr/lib/gcc-lib/i386-redhat-linux/3.2/../../../crti.o /usr/lib/gcc-lib/i386-redhat-linux/3.2/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/3.2 -L/usr/lib/gcc-lib/i386-redhat-linux/3.2/../../.. /tmp/ccA7Zevv.o -lobjc -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc-lib/i386-redhat-linux/3.2/crtend.o /usr/lib/gcc-lib/i386-redhat-linux/3.2/../../../crtn.o [s-okita@stoc objc]$ ./hello Hello Objective-C World
[s-okita@stoc objc]$ cat hello.m
#import <stdio.h>
#import <objc/Object.h>
@interface TestClass : Object
- (void) getMessage;
@end
@implementation TestClass
- (void) getMessage {
printf("Hello Objective-C World\n");
}
@end
int main(int argc, char *argv[]) {
id obj = [ TestClass alloc ];
[ obj getMessage ];
return 0;
}
[s-okita@stoc objc]$ gcc -Wall -O2 -o hello hello.m -lobjc
hello.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file
[s-okita@stoc objc]$ ./hello
Hello Objective-C World
Nil型が存在する。空のクラスを意味する。
基本はC言語と同様です。
基本はC言語と同様です。
基本はC言語と同様です。
基本はC言語と同様です。
基本はC言語と同様です。
基本はC言語と同様です。
C言語の上位言語であるためC言語のライブラリをそのまま利用できます。またGNU拡張やApple拡張が存在します。この記事は文法の概要を説明しているため、詳しいことは省略します。
メソッドの前にマイナス記号をつけるとインスタンスメソッドになります。プラス記号をつけるとクラスメソッドになります。
[s-okita@stoc objc]$ cat class_test.m #import <stdio.h> #import <objc/Object.h> @interface Person : Object { /* instance variable */ char *name; int age; } /* instance method */ - (char*) getName; - (int ) getAge; - (void ) setData : (char *) name : (int) age; @end @implementation Person - (char*) getName { return name; } - (int ) getAge { return age; } - (void ) setData : (char *) name : (int) age { /* C++/Javaのthisキーワード */ self->name = name; self->age = age; } @end int main(int argc, char *argv[]) { char *test_name = "satoshi"; /* []でメソッドを呼び出す方式をメッセージ方式と呼びます。 * これはSmalltalkの方式です。allocメソッドはC++/Javaのnewキーワード * と同様。Objectクラスに実装されています。 */ id obj = [ Person alloc ]; [ obj setData : test_name : 26 ]; printf("\n", [ obj getName ]); printf("0\n", [ obj getAge ]); return 0; } [s-okita@stoc objc]$ gcc -Wall -O2 -o class_test class_test.m -lobjc class_test.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file class_test.m: In function `-[Person setData::]': class_test.m:27: warning: local declaration of `name' hides instance variable class_test.m:28: warning: local declaration of `age' hides instance variable [s-okita@stoc objc]$ ./class_test satoshi 26
クラスの初期化を多くの言語ではコンストラクタ、開放をデストラクタなどと呼びます。名称は異なれObjective-Cにも同様な機能が備わっています。
[s-okita@stoc objc]$ cat new_test.m #include <stdio.h> #include <objc/Object.h> /* initializer sample */ @interface TestClass : Object { int a; } -(id)init; @end @implementation TestClass -(id)init { [super init]; a = 100; printf("instanciate!!!\n"); return self; } -(id)free { printf("destruct!!!\n"); return [super free]; } @end int main(int argc, char *argv[]) { id obj = [ TestClass alloc ]; [ obj init ]; [ obj free ]; [ [ TestClass new ] free ]; return 0; } [s-okita@stoc objc]$ gcc -Wall -O2 -o new_test new_test.m -lobjc [s-okita@stoc objc]$ ./new_test instanciate!!! destruct!!! instanciate!!! destruct!!!
クラスに情報と処理を実装するとそれを公開するかしないかなどのアクセス権を実装できます。これによりカプセル化を実現します。
[s-okita@stoc objc]$ cat access_privilege.m #import#import @interface SuperHoge : Object { /* default protect */ int defaultProtectedVal; @public int publicVal; @private int privateVal; @protected int protectedVal; } @end @interface SubHoge : SuperHoge -(id) init; -(int) getProtectedVal; @end @implementation SuperHoge @end @implementation SubHoge -(id) init { [ super init ]; return self; } -(int) getProtectedVal { return protectedVal; } @end int main(int argc, char *argv[]) { /* public access */ SuperHoge *obj = [ SuperHoge new ]; printf("0\n", obj->publicVal); /* protected access */ SubHoge *obj2 = [ SubHoge new ]; /* printf("0\n", [ obj2 getProtectedVal ] ); */ printf("0\n", [ obj2 getProtectedVal ]); return 0; } [s-okita@stoc objc]$ gcc -Wall -O2 -o access_privilege access_privilege.m -lobjc access_privilege.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file [s-okita@stoc objc]$ ./access_privilege 0 0
クラス変数は存在しません。C言語同様ヘッダファイルにglobal変数を定義します。
[s-okita@stoc cpp]$ cat static_test.m #include <stdio.h> #include <objc/Object.h> int static_val = 0; @interface TestClass : Object + (void) printHello; + (int) getCount; + (void) increment; @end @implementation TestClass + (void) printHello { printf("hello\n"); } + (int) getCount { return static_val; } + (void) increment { static_val++; } @end int main(int argc, char *argv[]) { [ TestClass printHello ]; [ TestClass increment ]; printf("0\n", [ TestClass getCount ]); return 0; } [s-okita@stoc cpp]$ gcc -Wall -O2 -o static_test static_test.m -lobjc [s-okita@stoc cpp]$ ./static_test hello 1
CoffeeクラスがDrinkクラスを継承しているため、Drinkクラスのメソッドが利用できます。 C++と同様にコロン(:)記号で継承を表します。
[s-okita@stoc objc]$ cat inheritance_test.m #import <stdio.h> #import <objc/Object.h> @interface Drink : Object { char * name; } - (void ) setName : (char *)name; - (char*) getName; @end @interface Coffee : Drink @end @implementation Drink - (void ) setName : (char *)name { self->name = name; } - (char*) getName { return name; } @end @implementation Coffee @end int main(int argc, char *argv[]) { id obj = [ Coffee alloc ]; [ obj setName : "java coffee" ]; printf("\n", [ obj getName ] ); return 0; } [s-okita@stoc objc]$ gcc -Wall -O2 -o inheritance_test inheritance_test.m -lobjc inheritance_test.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file inheritance_test.m: In function `-[Drink setName:]': inheritance_test.m:18: warning: local declaration of `name' hides instance variable [s-okita@stoc objc]$ ./inheritance_test java coffee
オブジェクト指向言語には、ポリモーフィズムを実現するための機能が備わっています。クラス、継承もそうですが、以下では実際にポリモーフィズムを実現している例を記述します。
個々のオブジェクトクラスには、Classクラスを保持しているためclassメソッドを呼び出すことができる。これを使えばメソッドのポリモーフィズムが実現できる。
[s-okita@stoc s-okita]$ cat ClassClass.m #import <stdio.h> #import <objc/Object.h> /* * Javaのjava.lang.Classと同じ機能 */ @interface PolySuper : Object + (void) printMessage; @end @interface PolyTest : PolySuper + (void) printMessage; @end @interface PolyTest2 : PolySuper + (void) printMessage; @end @implementation PolySuper + (void) printMessage { printf("PolySuper"); } @end @implementation PolyTest + (void) printMessage { printf("PolyTest\n"); } @end @implementation PolyTest2 + (void) printMessage { printf("PolyTest2\n"); } @end int main(int argc, char *argv[]) { /* classメソッドでClassクラスを生成 */ Class classClass = NULL; classClass = [ PolyTest class ]; /*メソッドのポリモーフィズム */ [ classClass printMessage ]; classClass = [ PolyTest2 class ]; [ classClass printMessage ]; return 0; } [s-okita@stoc s-okita]$ gcc -Wall -O2 -o ClassClass ClassClass.m -lobjc ClassClass.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file [s-okita@stoc s-okita]$ ./ClassClass PolyTest PolyTest2
ポリモーフィズムを実現するための機能です。子供のクラスで同じシグネチャのメソッドを作成するとその実体に対するメソッドを呼び出します。
[s-okita@stoc objc]$ cat override_test.m #import <stdio.h> #import <objc/Object.h> /* 本来はヘッダファイルに記述する */ @interface Pearent : Object - (void) message; @end @interface Child : Pearent /* オーバーライド */ - (void) message; @end @implementation Pearent - (void) message { printf("persion instance\n"); } @end @implementation Child - (void) message { printf("child instance\n"); } @end void call(id opaque) { [ opaque message ]; } int main(int argc, char *argv[]) { id test = NULL; test = [ Pearent alloc ]; call(test); test = [ Child alloc ]; call(test); return 0; } [s-okita@stoc objc]$ gcc -Wall -O2 -o override_test override_test.m -lobjc override_test.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file [s-okita@stoc objc]$ ./override_test persion instance child instance
メソッド名が同じでもシグネチャが異なれば、コンパイル時にプログラムが判断してそのメソッドを呼び出します。C++/Javaのようにメソッド名だけではオーバーロードが実現できません。以下のサンプルのようにラベルが必要になります。
[s-okita@stoc objc]$ cat overload_test.m #import#import @interface MathClass : Object - (int) add : (int ) a I : (int ) b; - (double) add : (double) a D : (double) b; @end @implementation MathClass - (int) add : (int ) a I : (int ) b { return a + b; } - (double) add : (double) a D : (double) b { return a + b; } @end int main(int argc, char *argv[]) { id obj = [ MathClass alloc ]; printf("0\n", [ obj add : 1 I: 1 ]); printf("0.000000\n", [ obj add : 1.0 D: 1.0]); return 0; } [s-okita@stoc objc]$ gcc -Wall -O2 -o overload_test overload_test.m -lobjc overload_test.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file [s-okita@stoc objc]$ ./overload_test 2 2.000000
オブジェクト指向において、契約を実現させる機能の1つです。オブジェクトには状態と動作を持たせますが、動作(メソッド)をもっていることを保証するために利用したりします。Objective-Cは、JavaでInterfaceと呼ばれるものをプロトコル(@protocol)として実現しています。 言語に依存せず「この言語で、契約を実現させる機能は何か?」という意識をもつと他の言語でも理解しやすくなると思います。
[s-okita@stoc objc]$ cat interface.m #import#import @protocol HTTPProtocol - (void) GET; - (void) POST; @end /* RFC2518 */ @protocol WEBDAV - (void) MKCOL; @end @interface MyWebClient : Object <WEBDAV> @end @implementation MyWebClient - (void) GET { printf("GET method\n"); } - (void) POST { printf("POST method\n"); } - (void) MKCOL { printf("MKCOL method\n"); } @end int main(int argc, char *argv[]) { MyWebClient *client = [ [ MyWebClient alloc] init ]; [ client GET ]; [ client POST ]; [ client MKCOL ]; [ client free]; return 0; } [s-okita@stoc objc]$ gcc -Wall -O2 -o interface interface.m -lobjc interface.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file [s-okita@stoc objc]$ ./interface GET method POST method MKCOL method
Classクラスと同様にポリモーフィズムを実現させるための機能です。コンパイル時にメソッド名はコーディングして決まっているため、それを利用して実行時にメソッドを決定する機能です。
[s-okita@stoc s-okita]$ cat Selector.m #import#import @interface Pearent : Object - (void) message; @end @interface Child : Pearent - (void) message; /* override */ @end @interface Child2 : Pearent - (void) message; /* override */ @end @implementation Pearent - (void) message { printf("Pearent\n"); } @end @implementation Child - (void) message { printf("Child\n"); } @end @implementation Child2 - (void) message { printf("Child2\n"); } @end int main(int argc, char *argv[]) { /* メソッドの内部表現を保持する。ポインタみたいなイメージ */ SEL abstructMessage = @selector( message ); [ [ Child alloc ] perform:abstructMessage ]; [ [ Child2 alloc ] perform:abstructMessage ]; return 0; } [s-okita@stoc s-okita]$ gcc -Wall -O2 -o Selector Selector.m -lobjc Selector.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file [s-okita@stoc s-okita]$ ./Selector Child Child2
継承以外にも既存のソースコードに手を入れることなく拡張できる機能があります。それがカテゴリです。
[s-okita@localhost objc]$ cat category_test.h category_test.m #import <objc/Object.h> @interface TestClass : Object -(void)message; @end @interface TestClass (Hoge) -(void)messageB; @end #import "category_test.h" #include@implementation TestClass -(void)message { printf("hello\n"); } @end @implementation TestClass (Hoge) - (void) messageB { printf("hello category\n"); } @end int main(int argc, char *argv[]) { TestClass *ts = [ TestClass alloc ]; [ ts message ]; [ ts messageB ]; [ ts free ]; return 0; } [s-okita@localhost objc]$ gcc -Wall -O2 -o category_test category_test.m -lobjc category_test.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file [s-okita@localhost objc]$ ./category_test hello hello category 別ファイル(category_sub.m)に新しいメソッドを作成する。
[s-okita@localhost objc]$ cat category_sub.m #import "category_test.h" @interface TestClass (FUGA) -(void) appendMessage; @end @implementation TestClass (FUGA) -(void) appendMessage { printf("append\n"); } @end[s-okita@localhost objc]$ gcc -Wall -O2 -o category_test category_sub.m category_test.m -lobjc category_sub.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header file category_test.m:1:2: warning: #import is obsolete, use an #ifndef wrapper in the header fileこれを再度コンパイルするとリンケージされて既存のクラスに機能追加をすることができます。この例の場合TestClassクラスにappendMessageメソッドを追加しました。もしmain()関数などが動的にメソッドを判断している場合はこの追加機能は非常に効果があります。このような簡単なサンプルでは分かりませんがこの延長上にOCP(OpenClosedPrincipal)というオブジェクト指向の最終形態が存在します。
gccでコンパイルしているためgdbでのデバックが可能です。gdbの使い方が分かればC言語と同様なデバックが可能です。ただ、Objective-Cの最大の特徴が非常に動的な言語であるため動的にコーディングすればするほど、解析が困難になる可能性があります。
今回、数日かけてObjective-C入門を記述してみましたが、Objective-Cの特徴の1つは、C言語を忘れずにオブジェクト指向技術を身に付けることが可能ということです。
プログラミングをするとC言語はOSやTCP/IPの実装など今日のソフトウェアの基幹を占めているため避けてはとおれません。C++は学習する際やオープンソースを解読する際にC言語の知識が必要となりますが、実際にはC言語を使わずにコーディングできます。またC++同様、C言語上位互換であるにも関わらず言語仕様が非常に小さいことが理解できました。
今日のJava言語は仕様変更と多くのオープンソースソフトウェアや利便性で主力言語となっています。しかし発展するためにライブラリと仕様拡張をしなければならないというデメリットも背負わなければならない状態になってきました。C言語、Smalltalk、Objective-Cは「ソフトウェアに制御を持たせるのではなく、技術者に制御を持たせる」という原始的思想があるため膨大な仕様拡張はされてきませんでした。
Objective-C,C言語,C++,Javaを例にとると、それぞれの言語拡張がどのようにされているかなどを再認識できました。
また新たに「ソフトウェア開発効率を求めるがゆえに簡単に実装できるライブラリや仕様拡張をおこなうと、言語の寿命が短くなるのではないか?」という問いをこの言語から投げかけられました。
少し「言語は方言である」という問いの解答に近づいた気がします。
2004-11-06 15:05
Objective-C Beginner's Guide(英語)
OBJECT-ORIENTED PROGRAMMING AND THE OBJECTIVE-C LANGUAGE(英語)