65
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Obj-CのLightweight Generics

Last updated at Posted at 2015-10-06

Xcode 7から使えるようになったObjective-Cの「Lightweight」Genericsですが、どうも公式のドキュメントが見つけられず(存在しない?)、今ひとつ構文がわからなかったので、Appleのヘッダを見たりネットの情報を漁ってみたりしたのを、自分なりにまとめました。

Apple公式の情報

ポイント

  • クラスに対して利用可能。プロトコルやメソッドには利用できないっぽい。
  • あくまでもコンパイル時の型チェック用。コンパイルされた結果は 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が使えるともっと使えそうな気がするんですけど‥‥。惜しいなあ。

65
56
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
65
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?