独自の検索メソッドを追加したり、NSArrayを拡張したい時があると思います。今回はNSArrayの拡張について説明します。

NSArrayのSubclassを作ることはできない

SDKのドキュメントにもあるのですが、NSArrayを継承して独自のクラスを作ることはできません。なのでSubclass化してメソッドを追加する方法は選択肢から排除されます。

NSArrayのカテゴリメソッドを追加する

継承はできないですが、カテゴリメソッドを追加することはできます。

@implementation NSArray (ST)

// 配列の要素の順番を逆にして返すカテゴリメソッド
- (NSArray *)reversedArray {
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:[self count]];
    NSEnumerator *enumerator = [self reverseObjectEnumerator];
    for (id element in enumerator) {
        [array addObject:element];
    }
    return array;
}

カテゴリメソッドは配列内に格納されるオブジェクトの型に依存しない、あくまで汎用的な用途に限ったほうがいいでしょう。

汎用的でないカテゴリメソッドの例

// PSAccountオブジェクトのnameプロパティが一致するものを返す
- (PSAccount *)findAccountByName:(NSString *)name {
    for (id object in self) {
        if ([object isKindOfClass:[PSAccount class]]) {
            PSAccount *account = (PSAccount *)object;
            if ([account.name isEqualToString:name]) {
                return account;
            }
        }
    }
    return nil;
}

このようなカテゴリメソッドを作るよりは、次に述べるようにNSArrayオブジェクトを内包するクラスを作ったほうが良いと思います。

NSArrayオブジェクトを内包するクラスを作る

特定の型のオブジェクトのみを扱う配列は、以下のようにNSObjectを継承して独自のArrayクラスを宣言すると良いと思います。

// STAccountArray.h
@interface PSAccountArray : NSObject

- (NSUInteger)count;
- (void)addAccount:(PSAccount *)account;
- (void)removeAccount:(PSAccount *)account;
- (PSAccount *)accountAtIndex:(NSUInteger)index;
- (PSAccount *)findByName:(NSString *)name;

@end

実装の方で内包するNSArrayオブジェクトにアクセスします。

// STAccountArray.m
@implementation PSAccountArray {
    __strong NSArray *_array;
}

- (id)init {
    self = [super init];
    if (self) {
        _array = [NSMutableArray arrayWithCapacity:10];
    }
    return self;
}

- (NSUInteger)count {
    return _array.count;
}

- (void)addAccount:(PSAccount *)account {
    [_array addObject:account];
}

- (void)removeAccount:(PSAccount *)account {
    [_array removeObject:account];
}

- (PSAccount *)accountAtIndex:(NSUInteger)index {
    return [_array objectAtIndex:index];
}

- (PSAccount *)findByName:(NSString *)name {
    for (PSAccount *account in _array) {
        if ([account.name isEqualToString:name]) {
            return account;
        }
    }
    return nil;
}

@end

ちょっと二度手間な感じがして面倒な気がしますが、自分はこういう独自配列クラスを割と作ります。全体的なコードの読みやすさの向上につながると思うので。この例では配列にPSAccount以外のオブジェクトの追加を防ぐことができるのも良いですね。(キャストを使って強引に入れることはできますが)

独自配列でfor inが使えるようにする

上記の独自配列クラスはそのままだとfor inで要素を取り出すことができません。以下のメソッドを宣言・実装すると可能になります。

ヘッダファイルでの宣言

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len;

ソースファイルでの宣言

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len {
    return [_array countByEnumeratingWithState:state objects:buffer count:len];
}

以上、配列の拡張についてでした。