普通に iPhone アプリを使うなら swift でいいんだろうが、既存の C/C++ ライブラリを組み込んで使うような場合などは、Objective-C も検討せねばならないだろう。
特にレガシーなプロジェクトなぞ、本体が Objective-C で書かれているので、swift に移植するにせよ、ある程度の Objective-C の理解は必須だろう。
なぜ Objective-C はとっつきにくいのか?
ところで、Objective-C のクセの強さは、万人の知るところだが、これは歴史的背景を考えると理解しやすいかもしれない。
C にオブジェクト指向の思想を取り込むために、大雑把に二つのアプローチがあったようだ。
一つは言語自体の設計を変えてしまう方法。これが現在の主流で、 C++ の源流となっている。
もう一つは、C のコンパイラー自体には大きな変更を加えず、文法的な表記に特殊仕様を入れ、パース時にこれを解釈して、オブジェクトを取り扱えるようにする方法。Objective-C は、どちらかといえば、こちらだろう。
だから、C++ 感覚で取り組もうとすると訳のわからないことになってしまう。
また、Objective-C がとっつきにくいのは、ビギナー向けのほとんどのサンプルが Mac や iPhone の GUI アプリだという点。これだと、Objective-C の勉強というよりも cocoa などの勉強になってしまい、Objective-C の言語の本質みたいなものが掴みにくい。
サンプル
というわけで、GUI 一切なしのコマンドラインプログラムを試しに書いてみた。
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <stdio.h>
// Person クラスの宣言
@interface Person : NSObject {
NSString *name;
}
- (void)setName :(NSString *)Name;
- (void)printName;
@end
// Person クラスの実装
@implementation Person : NSObject
- (void)setName :(NSString *)Name {
name = Name;
}
- (void)printName {
printf("My name is %s \n", [name UTF8String]);
}
@end
// 実行プログラム
int main(void) {
id person;
person = [[Person alloc] init];//Person *person = new Person(); などとはしない
[person setName:@"akiba"];
[person printName];
return 0;
}
これを Xcode 上で実行するとこんな感じになる。
Person クラスを実体化して、フィールド変数の name をセットした後、ゲッター経由でこの値を取得・表示するというよくあるプログラムだ。
GUI がないから、ワンファイルにすっきりと収まっているが、(Java や C++ に慣れた目から見ると)違和感を感じるところがちょいちょいある。
まず、どうということはないクラス Person でも NSObject を継承している点。またクラスを定義するときに@ディレクティブを多用している。
Objective-C の「C のコンパイラー自体には大きな変更を加えず、文法的な表記に特殊仕様を入れ、パース時にこれを解釈して、オブジェクトを取り扱えるようにする」という歴史的経緯に由来する特徴がよく出ているのではないかと思う。
ところで、最も違和感のあるのはこの箇所だろう。
person = [[Person alloc] init];
オブジェクトに対してメッセージを送ることで機能を発現させているわけだが、これも歴史的な背景があり、 SmallTalk の影響らしい。
ただし、id 型は必ずしも使う必要はなく、
Person *person = [[Person alloc] init];
としても動きます。
また、Objective-C は C の拡張なので、C の関数も当然使える。実際、printf は C と同じように動作している。
その他、注意しておきたいところ
フィールド変数(インスタンス変数)
name のセッターは setName になってますが、フィールド変数の先頭文字を「大文字」にするのはお約束のようです。
ここら辺は Java と一緒でしょうか。
UTF8String がわからん、という声も聞きますが、name 自体は NSString (へのポインタ)です。標準 C の String に変換する必要があるので、ここで変換をかけているわけです。
なお、インスタンス変数の宣言の仕方などについては、この質疑応答が凄まじくよくまとまっています。
m と mm の違い
Objective-C のファイルは .m .mm の二つがあり得ます。
この記事がわかりやすいんですが、会員限定かな。
簡単にいえば、.m が objective-C ファイル本来の拡張子、.mm の方は C++ のファイルを取り込む際に使用する拡張子です。
継承とカテゴリ
オブジェクト指向の特徴の一つは継承だが、Objective-C はここら辺は素直だ。
よくわからんのは「カテゴリクラス」。
Person(hogehoge)
とあった場合、このクラスは元の Person のカテゴリクラスです。
元の場所とは違った場所でメソッドが追加できます。(同じ理由だが分割することもできる)
情報の古い(アップデートされてない)記事が多い
Obj-C ユーザーが少ない理由に、記事がそもそも少ないし、あっても古い(アップデートされてない)というのも一因になっていると思う。
例えば、よく「Obj-C ではクラス変数という概念がない」などと紹介されているが、2016 以降(OS 10.12 〜)はある。
公式アナウンスはこちら。
使い方は別記事で説明するかもしれないが、@property(class) などとする。
若干クセはあるのだが、普通にクラス変数っぽく使える。あるじゃん。
また、objective-c 自体も文法が(主に 2010 年代に)モダンに改変されており、@property や @synthesize なども使い方が微妙に変わっている。
この図はこちらの記事からお借りしてきました。わかりやすいですね、感謝!
多重継承の禁止とプロトコル
継承が出てきたので、書いておくと Obj-C では多重継承、つまり複数のクラスを親クラスにすることができないらしい。
多重継承の代わりにプロトコルで代用しなさいよ、というのがアップルの教え。
サンプルコードなどで
@interface ViewController : NSViewController<NSTableViewDataSource>
という書き方をよく見かけるが、<> 内がプロトコル。
このまま続けてもいいのだが、まとまり悪くなるのでここでいったん切ろう。
ワイもそうだが、IT 業界にいる限り Objective-C だけいじっていればいいというものではない。
というか実際に多い案件は、別の言語だったりする。
だから、この記事は「ちょっと objective-C 思い出すときに読み返す」ときに役にたつ内容にしておく。
その他
@property 表記
アクセサー(ゲッターやセッターのこと)を定義できる。慣れれば便利なのだろうが、Java あたり読み書きしているとすぐにこの規約を忘れる。
この記事読んでね。
クラスエクステンションの () 表記
忘れやすいのがクラスエクステンションの時に使われる () 。
詳しくはこの記事あたり参照。
なんでヘッダーで定義された宣言を .m ファイルでもう一回やる必要があるのか?疑問に思ったら読むといいと思う。
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
// 名前
@property (nonatomic) NSString *name;
// 名前と年齢をログに出力する
- (void)displayProfile;
@end
Person.m
#import <Foundation/Foundation.h>
@interface Person ()
// 年齢
@property (nonatomic) NSInteger age;
// 名前をログに出力する
- (void)displayName;
// 年齢をログに出力する
- (void)displayAge;
@end
@implementation Person
……
@end
要するに Obj-C には Public だの Private がないため、ヘッダーファイルではなく実体ファイルの方でメソッド・メンバー変数を定義することで外部クラスからのアクセス制限を行っているという理屈です。
Person.h
@interface Person: NSObject
@end
Person.m
@interface Person()
@end
@implementation Person
@end
という構造が見えてますか?
強調して書くとこういう仕組み。
強調して、と書いたがプロジェクトを作成するとき AppDelegate は () をつけて生成してくれますね。
カテゴリの () 表記
ここが紛らわしいと思うのだが、@interface () はクラスエクステンションだけでなくカテゴリでも使われる。
ただ、こちらはファイル名自体が ClassA+ClassB.h のようになっているので、それで判断できるかな。
『カテゴリーとクラスエクステンション』参照。