Xcode 7から使えるようになったObjective-Cの「Lightweight」Genericsですが、どうも公式のドキュメントが見つけられず(存在しない?)、今ひとつ構文がわからなかったので、Appleのヘッダを見たりネットの情報を漁ってみたりしたのを、自分なりにまとめました。
Apple公式の情報
- Xcode 7 Release Notes ←ほんの少しだけ
- WWDC 2015 Video: Swift and Objective-C Interoperability ←21:47〜 (コメントで教えてもらいました)
ポイント
- クラスに対して利用可能。プロトコルやメソッドには利用できないっぽい。
- あくまでもコンパイル時の型チェック用。コンパイルされた結果は
id
になる。なお、型チェックに引っかかったときは警告が出る。 -
@interface
(ヘッダ側)で仮型引数が使える。@implement
の方(実装側)では使えない。 - クラスの定義時に必要に応じて共変性と反変性を指定する。
Genericsのクラスを使う場合
クラス名<型引数>
という構文ですが、型引数の部分はポインタになるので注意。
さらにそのクラスの型もポインタとして使うので、結局、 クラス名<型 *> *
という形になります。
例えば、NSStringを要素に持つNSArrayなら次のように書きます。
NSArray<NSString *> *strings = @[@"Foo", @"Bar", @"Baz"];
NSString *first = [strings objectAtIndex:0];
なお、型引数付きで宣言したものと、型引数なしで宣言したものは警告なしに変換可能です。
NSArray<NSString *> *strings = ...;
NSArray *arrays = ...;
arrays = strings; // OK
strings = arrays; // OK
今までのコードやライブラリは型引数なしのまま、これから書くコードは型引数付きにして、というように混在させておいて、徐々に型引数ありへ移行させていくことができますね。一気に全部書きなおさなくていいのはありがたいです。
Genericsを使ったクラスを作成する場合
ヘッダ側で @interface クラス名<(variance) 仮型引数> : 親クラス
という形式で仮型引数を書きます。
(variance)は共変性と反変性の指定(後述)。仮型引数が複数ある場合は <(variance) 仮型引数1, (variance) 仮型引数2>
というようにコンマで区切ります。
仮型引数名としては、Appleは T
のような1文字ではなく、 ObjectType
というのを好んで使っているようですが、別になんでもいいと思います。この仮型引数を、メソッドの引数や戻り値の型として使えます。
※仮型引数にはポインタの *
は付けません。
こんな感じです。
@interface Queue<__covariant ObjectType> : NSObject
- (instancetype)initWithArray:(NSArray<ObjectType> *)array;
- (ObjectType)next;
@end
一方、実装側では仮型引数は使えません。仮型引数の部分は id
に置き換えて実装します。
こんな感じです。
@implementation Queue
- (instancetype)initWithArray:(NSArray *)array {
self = [super init];
if (self) {
_array = [array mutableCopy];
}
return self;
}
- (id)next {
if ([self.array count] > 0) {
id result = [self.array firstObject];
[self.array removeObjectAtIndex:0];
return result;
} else {
return nil;
}
}
@end
共変性と反変性の指定
大雑把にいえば、継承関係のあるクラスをそれぞれGenericsの型引数に指定したものがある場合に、その一方をもう一方として利用できるかどうかの指定です。
例として、親クラス Animal
とその派生クラス Dog
があった場合に、
-
Hoge<Dog>
をHoge<Animal>
として利用してよいなら共変 →__covariant
キーワードを指定 -
Hoge<Animal>
をHoge<Dog>
として利用してよいなら反変 →__contravariant
キーワードを指定 - どちらでもないなら不変 → キーワードを何も付けない。
とします。
1つ前で例に挙げた Queue
クラスは __covariant
キーワードで共変性を指定してあるので、 Queue<Dog>
を Queue<Animal>
として利用できます。
もし、犬でも猫でもなんでもいいので動物が入ったキューを受け取って、 next
で次の動物を取り出して処理する、というメソッドがあれば、そこに犬ばかり入ったキューを渡しても処理できるはずですね。
逆に、次の Handler
クラスは反変性の指定の例です。
@interface Handler<__contravariant ObjectType> : NSObject
- (instancetype)initWithBlock:(void(^)(ObjectType))block;
- (void)handle:(ObjectType)obj;
@end
このクラスは ObjectType
の扱い方を知っているクラスです。
Handler<Dog>
なら handle:
メソッドの引数に犬を渡すと何か処理を行います。
Handler<Animal>
なら犬でも猫でも受け取って処理できます。
こちらは __contravariant
で反変性を指定してあるので、 Handler<Animal>
を Handler<Dog>
として利用できます。つまり、犬を扱えるものが欲しいところに、動物なら何でも扱えるものを渡してもいいですよ、ということです。
感想
まだ使いこなせてないのですが、例えばメソッドの引数が今まで NSArray
だったのが、 NSArray<NSString *>
や NSArray<NSNumber *>
のように書けるので、パッと見て何を渡せばいいかがわかるようになるだけでもうれしいと思いました。
ただ、プロトコルやメソッドにもGenericsが使えるともっと使えそうな気がするんですけど‥‥。惜しいなあ。