[Objective-C] WMGenericCollectionでジェネリクスっぽくコレクションを扱う

2014.03.01

この記事での前提環境はXcode5です。

NSArrayの不満点

Objective-Cの標準コレクションクラスは基本的に一つのインスタンスにすべてのオブジェクト型が入れられます。

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSArray *variableObjects = @[@"string", @(111), [NSObject new], [NSDate date]];
    NSLog(@"%@", variableObjects);
}

このvariableObjectsから値を取り出すときにはまずid型として取り出されるために、

NSLog(@"%d", variableObjects[0].length);

などとしても

Property 'length' not found on object of type 'id'

というコンパイル時エラーが起こるため、具体的なクラスのメソッドを呼び出すときには詳細な型にキャストする必要が出てきます。

さらに、NSStringのNSArrayから文字列長NSArrayを返すメソッドについて考えてみます。

- (NSArray *)lengthArrayFromStrings:(NSArray *)strings
{   
    NSMutableArray *lengths = [NSMutableArray new];
    for (NSString *string in strings) {
        if ([string isKindOfClass:[NSString class]]) {
            [lengths addObject:@(string.length)]
        }
    }
    return lengths;
}

このプログラムを書いた人がメソッドの呼び出し側に期待する事は引数stringsにNSString、もしくはそのサブクラスを要素に持つNSArrayが入っていることです。

先ほど見たようにNSArrayには様々な型のオブジェクトを入れられますので、- (BOOL)isKindOfClass:メソッドを用いてNSString以外の要素の長さを返り値のNSArrayに含まないようにしています。

しかし、決まった型のNSArrayに対して型を確かめることでランタイム時のエラーに備えるというのは型情報をコンパイル時に活かしきれていないという点では不満が残ります。

この不満に対する解決法として、WMGenericCollectionというライブラリを今回取り上げます。

WMGenericCollection

非ARC環境下での導入

非ARC環境では導入は至ってシンプルです。 githubのリポジトリからソースコードをダウンロードしてきて、Collections/Cocoa headersのディレクトリからWMGenericArray.hのヘッダファイルをプロジェクトにインポートしてあげます。

#import "ViewController.h"
#import "WMGenericArray.h"

WMGENERICARRAY_INTERFACE(NSString *, NSStringArray, NSMutableStringArray)
WMGENERICARRAY_SYNTHESIZE(NSString *, NSStringArray, NSMutableStringArray);

WMGENERICARRAY_INTERFACE(NSURL *, NSURLArray, NSMutableURLArray)
WMGENERICARRAY_SYNTHESIZE(NSURL *, NSURLArray, NSMutableURLArray);

@interface ViewController ()

@end

@implementation ViewController

#pragma mark - Lifecycle methods

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSStringArray *strings = (NSStringArray *)@[@"https://", @"http://"];

    NSLog(@"%@", strings);

    NSURLArray *URLs = [self URLArrayFromStrings:strings];

    NSLog(@"%@", URLs[1].absoluteURL);
}

#pragma mark - Private methods

- (NSURLArray *)URLArrayFromStrings:(NSStringArray *)strings
{
    NSMutableURLArray *URLs = [NSMutableURLArray array];
    for (NSString *string in strings) {
        [URLs addObject:[NSURL URLWithString:string]];
    }
    return [NSURLArray arrayWithArray:(NSURLArray*)URLs];
}

@end

本来ならばURLs[1].absoluteURLはErrorがでるところなのですが、型付きコレクションを導入したことで要素のクラスで定義されているメソッドを呼び出せます。更にコード補完でレコメンドもされるようになります。

CodeAutoCompletion

また、型の異なるクラスを代入しようとするとWarningで警告を発してくれます。

CodeWarning

ARC環境下での導入

ARC環境下で非ARC環境下のようにうまくは行きません。元のソースコード(MITライセンス)を一部改変したヘッダファイルをこちらのGithubからダウンロードしてインポートしてください。

改変ファイルではc言語配列からNSArrayを取り出すメソッド引数に__strongディレクティブを加えてあります。 また、何故か追加されていなかったfirstObjectのメソッドも追加しています。

NSSet, NSDictionaryに対応する型付コレクションクラス

NSSet, NSDictionaryに対応したマクロも準備されています。こちらのマクロもARC環境下では難があるので修正を加えたファイルを先ほどのGithubに公開してあります。使用例はこちらのサンプルコードを御覧ください。

参考サイト

w-m/WMGenericCollection - Github

iphone - What does “Must explicitly describe intended ownership of an object array parameter” mean and how can I fix it? - Stack Overflow