Developer's Blog

NSArray のサブクラスでコードをシンプルに

Cocoa / Objective-C プログラミングをしていると、NSArray や NSSet、NSDictionary あたりはよくお世話になりますよね。これらオブジェクトの集合を扱うクラスは一般に「コレクション」と呼ばれています。Cocoa / Objective-C に限らず、コレクションをうまく扱うとコーディングのスピードもプログラムの実行スピードも大きく改善できます。

今回は、既存のコレクションクラスを使うのではなく、独自のコレクションクラスを作る方法とそのメリットを、NSArray のサブクラスを例として紹介します。

NSArray のサブクラス

Objective-C はカテゴリによって既存のクラスに機能を追加できるので、サブクラスを作る場面は限られています。カテゴリがあるにも関わらず、サブクラスを作る理由。それはまさに「独自のコレクションクラス」を作るためです。どこにどんな風にオブジェクトの集合を記憶しておくか、そこにどうやってアクセスするかの実装をカスタマイズできるわけです。

ちょっと大げさになりましたが、NSArray のサブクラスを作るのはとても簡単です。NSArray のサブクラスを宣言し、そのクラスで count と objectAtIndex: の2つのメソッドを実装するだけ。つまり、次の2つの情報があれば独自の NSArray を作れてしまいます。

  • いくつの要素があるのか
  • n番目の要素は何か?

もちろん containsObject: や subarrayWithRange: などもちゃんと動作するので、基本的にフレームワークで提供されている NSArray と同じように利用できます。

利用例: 2つの NSArray をつなげた NSArray

どんな利用法があるでしょうか?

たとえば次のような状況を考えてみましょう。

  • array1 と array2 という2つの NSMutableArray を管理している。
  • その array1 と array2 を連結した NSArray を array12 として利用したい。
  • array1 または array2 の内容が変更されたら、array12 にも反映されるようにしたい。

いろいろな方法が考えられますね。たとえば、array12にアクセスするたびに array1 と array2 を連結した新しい NSArray を生成するのもよいでしょう。array1 と array2 への変更を常に監視して、その変更を array12 に反映するのもいいでしょう。

しかし、せっかくなので NSArray のサブクラス JoinedArray として実現してみましょう。上記の要件なら、必要な2つの情報「いくつの要素があるのか」「n番目の要素は何か?」は明らかです。

JoinedArray の特徴は、いったんインスタンスを生成してしまえば、元になっている array1 と array2 をもとに常に正しい情報を示すことです。アクセスのたびに新たなオブジェクトを生成する必要も、変更されたかどうかを気にする必要もありません。

考えられる用途はいろいろ

繰り返しになりますが、次の2点さえはっきりしていれば独自の NSArray サブクラスを作れます。

  • いくつの要素があるのか
  • n番目の要素は何か?

ということは、他にもいろいろな独自 NSArray が作れそうです。たとえば次のようなものはどうでしょうか。もし興味があったら実装してみてください。

  • まったく同じオブジェクトが N 個ある NSArray。
  • ある NSArray の要素を一定のルールで変換した結果(たとえばあるプロパティ値)からなる NSArray。
  • 各要素に初めてアクセスしたときにその要素を生成する NSArray。

なお、NSArray のサブクラス化については NSArray のクラスリファレンスに方法や注意事項も含めて書いてあるので一度目を通してみることをおすすめします。

では最後に JoinedArray の実装例を掲載しておきます。とても簡単ですね。

@interface JoinedArray : NSArray
{
    NSArray *array1;
    NSArray *array2;
}

- (id)initWithArray1:(NSArray *)array1 array2:(NSArray *)array2;

@end

@implementation JoinedArray

- (id)initWithArray1:(NSArray *)a1 array2:(NSArray *)a2
{
    self = [super init];
    if (self) {
        array1 = [a1 retain];
        array2 = [a2 retain];
    }
    return self;
}

- (NSUInteger)count
{
    return [array1 count] + [array2 count];
}

- (id)objectAtIndex:(NSUInteger)index
{
    if (index < [array1 count]) {
        return [array1 objectAtIndex:index];
    }
    NSUInteger indexInArray2 = index - [array1 count];
    return [array2 objectAtIndex:indexInArray2];
}

- (void)dealloc
{
    [array1 release];
    [array2 release];
    [super dealloc];
}

@end

Copyright © 2019 Fenrir Inc. All rights reserved.