☆Cocoa Foundationクラス(等):「集合」
これは、 Collections Programming Topics をiOS専用に翻訳(意訳)・手入れしたものである。

「集合」(collection)
Cocoa Touchにおいて、集合はFoundationクラスで、オブジェクトの格納と管理のために使われる。 その主要な役割は、配列(NSArray)・辞書(NSDictionary)・セット(NSSet)でオブジェクトを保存することである。 下図は、配列・辞書・セットの模式図である。

図1




「集合」の概要
集合はいくつかの特徴を共有する。 すべての集合はオブジェクトのみを保持し、 可変(Mutable)もしくは固定値(不変;immutable)値である。 共通する特徴は以下のとおり。 可変集合には以下のタスクが追加される。 多くの特徴を共有する集合だが、重要な違いもある。 それらを理解して使い分けるべきである。

要素番号によるアクセスと簡単な列挙要素:配列
NSArrayやNSMutableArrayのような配列は、順序だった集合である。 それらの要素は要素番号で示す。 テーブル・ビューでの情報表示等で使う。

データを任意のキーと結びつける:辞書
NSDictionaryやNSMutableDictionaryのような辞書は、順序だってない集合である。 それらの要素にはキー値でアクセスする。 辞書は、キーによって意味づけされる値を保管する。

高速挿入、削除と重複がないことのチェック:セット
NSSetやNSMutableSetおよびNSCountedSetのようなセットは、順序だってないオブジェクトである。 それは、高速な挿入と削除操作を提供する。 また、集合中でオブジェクトがどこにあるかを高速に見ることができる。 NSSetとNSMutableSetは重複のないオブジェクトの集合を格納する。 NSCountedSetは重複があるオブジェクトの集合を格納する。 たとえば、あなたがいくつかの都市オブジェクトを持ち、それぞれを一度だけ訪問したいとする。 あなたがセットで訪問する各々の都市を保存するならば、そこを訪問したかどうか、迅速かつ容易に見ることができる。

配列のサブセットを保存する:インデックスセット
NSIndexSetやNSMutableIndexSetのようなインデックスセットは、配列の能力を広げる補助的オブジェクトである。 それらは、新しい配列を造るのではなく、配列の要素番号を別途記録することによって、配列のサブセットを保存することができる。 ユーザーがエントリのリストから複数のエントリを選ぶのを許すために、インデックスセットを使うかもしれない。 たとえば、テーブル・ビューを持ち、ユーザーが列のいくつかを選ぶのを許可するとする。 その列が配列として保存されるので、その配列へのインデックスセットとして選択を保存することができる。

入れ子になった(ネストした)配列を通したパス(経路)の保存:インデックスパス
インデックスパスは、情報の位置を保管する。 たとえば、インデックス経路1.4.3.2は、以下の経路を示す:


UITableViewクラスは、テーブル・ビュー中で場所を保存するために、インデックスパスを広範囲に利用する。

集合の機能:コピーと列挙
各クラスに特有の動作に加え、集合クラスでほぼ共通のタスクがいくつかある。 その代表が集合のコピーと内容の列挙である。
ある集合から別の新しい集合を作る必要があるとき、浅い(shallow)コピーか深い(deep)コピーを選ぶことができる。 浅いコピーではアドレスのみコピーされる。 従って、その実内容を変更すると、それを参照している物全てが影響を受ける。 深いコピーでは、実内容も新たにコピーで作成される。 従って、1つの実内容を変更しても、他の物には影響しない。 選択した項目上で、ある状態またはあるactionを実行するために集合内のそれぞれのアイテムをチェックする必要があるなら、 集合の内容を列挙する方法のうちの1つを使用することができる。 列挙の2つの主なメソッドは、高速列挙(fast enumeration)とブロック列挙である。


配列:順序だった集合
配列は、オブジェクトの順序だった集合である。 たとえば、配列によって図2に含まれるオブジェクトは猫と犬オブジェクトのどんな組合せでもありえる、 そして、配列が可変ならば、あなたはより多くの犬オブジェクトを加えることができる。 オブジェクトは、同じ型である必要はない。

図2


配列の基礎
NSArray は、内容が変わらない配列を管理する。 つまり、配列を作った後、オブジェクトの追加・削除・置換は出来ない。 しかし、個々のオブジェクトの内容は修正することができる。

NSMutableArrayは、可変配列を管理する。 それは、自動的に必要に応じて記憶を割り当てて、いつでもオブジェクトの追加・削除を許す。 たとえば、ちょうど一つの犬オブジェクトを含むNSMutableArrayがあれば、 あなたはもう一匹の犬か猫か他の物を加えることができる。 一般に、NSArrayで出来ることは全てNSMutableArrayでも出来る。 配列が1つずつ変わるか、初期化するに多くの時間がかかるほど非常に大きいならば、可変配列を使うべきである。

イニシャライザinitWithArray またはコンビニエンスコンストラクタarrayWithArray:を使って 他の配列から、1種類の配列のインスタンスを簡単に作ることができる。
たとえば、NSArray myArray のインスタンスがある場合、以下ようにして可変コピーを作成することができる:
    NSMutableArray *myMutableArray = [NSMutableArray arrayWithArray:myArray];
一般に、配列の1つをNSArray または NSMutableArrayにメッセージを送ることで配列をインスタンス化する。 配列は引数条件をパスした要素を含む配列を返す。
NSMutableArrayに1つのオブジェクトを追加したときは、オブジェクトは深くコピーされない。 (ただし、initWithArray:copyItems:の引数でYESをした時は除く)。
オブジェクトは追加されるときにretainメッセージを受け取る。 配列がreleseされるとき、各々の要素にもreleaseメッセージが送られる。
     lblArray=[[NSMutableArray alloc]init];
    
    ループ
    {
        UILabel *lbl=[[UILabel alloc]init]; // lblの実体が確保される
        ~
        [lblArray addObject:lbl];   // lblはretainされる
        [lbl release];              // 実開放はlblArrayのrelease時
    }
この例の場合、配列lblArrayに加えられた個別のオブジェクトlblはaddObject:した時点でretainされる。 そのため、その直後にreleaseメッセージを発行してもその場では解放されない。 配列lblArrayがreleaseされるとき、要素になっている全てのlblにreleaseが発行され、 そこで初めて実解放される。

もしrelease後もその要素実体にアクセスしたいならば、その前に要素に対してretainを送っておく必要がある。 そうしないと、アクセス時にランタイムエラーが発生する。

可変配列
可変配列の主メソッドは以下の通りであり、それは要素の追加・置換・削除を提供する。
addObject: 追加
insertObject:atIndex: 挿入
removeLastObject 最後の要素の削除
removeObjectAtIndex: 指定位置の要素を削除
replaceObjectAtIndex:withObject: 指定位置の要素を置き換え
出来るだけaddObject:とremoveLastObjectメソッドを使うべきである。 なぜなら、配列の最後への追加と削除は、中間へのそれより速いからである。

配列の利用
配列内ではcountとobjectAtIndex:が、他の全てのメソッドのための基本処理を提供する。 たとえば、NSStringオブジェクトの配列があるとき、3番目の文字列にアクセスするには次のようにする:
    NSString *someString=[arrayOfStrings objectAtIndex: 2]; 
NSArrayのメソッド objectEnumerator と reverseObjectEnumerator は配列要素への連続的なアクセスを提供する。 この2つは取り出す方向だけが違う。
同じように、NSArrayのメソッドmakeObjectsPerformSelector: と makeObjectsPerformSelector:withObject:は 配列内の全てのオブジェクトにメッセージを送る。
subarrayWithRange:配列の一部を引き抜いてサブセットを作る
componentsJoinedByString:配列要素がNSStringの場合、それを1つの文字列に連結する
isEqualToArray:2つの配列を比較する
firstObjectCommonWithArray:レシーバーの配列の要素の中で、引数の配列と一致する最初の要素を返す
arrayByAddingObjectsFromArray:既存の配列の後ろに別配列を結合して新規の配列を作成する
arrayByAddingObject:
既存の配列の後ろに1つの要素を結合して新規の配列を作成する

2つの配列中のどこにオブジェクトが存在するか決定するにはindexOfObject: と indexOfObjectIdenticalTo:の2つのメソッドがある。 それらの派生メソッド indexOfObject:inRange: と indexOfObjectIdenticalTo:inRange: は配列中の特定範囲を検索することが出来る。 indexOfObject: メソッドは配列中要素にisEqualメッセージを送ることで等価性を検証する。 indexOfObjectIdenticalTo: メソッドはポインターの比較で等価性を検証する。 その違いをリスト4に示す。
リスト 4 配列中オブジェクトの検索

    #define yes0    @"yes"
    #define yes1    @"YES"
    NSString *yes2    = [NSString stringWithFormat:@"%@", yes1]; 
    NSArray *yesArray = [NSArray arrayWithObjects: yes0, yes1, yes2, nil]; 
    NSUInteger index; 
    index = [yesArray indexOfObject:yes2];              // index is 1 
    index = [yesArray indexOfObjectIdenticalTo:yes2];   // index is 2 
配列のソート
ある基準に基づいて配列をソートする必要があるかも知れない。 たとえば、文字列をアルファベット順に並べる、または数字を昇順または降順に並べる必要があるかもしれない。 図3は姓/名順でソートした配列を示している。 Cocoaは配列内容のソートに便利な方法としてソート記述子(sort descriptors)とブロック(blocks)とセレクター(selectors)を提供する。

図3 配列のソート


ソート記述子によるソート
NSSortDescriptorのインスタンスであるソート記述子は、簡便にソート順を記述する抽象的な方法を提供する。 最小限のコード記述でほとんどのソート操作を簡単に実現できる。 Cocoaの作るソート内容、たとえばテーブルビューのようなものとともにソート記述子を使うことが出来る。 また、CoreDataとフェッチ要求の結果の順を使うことも出来る。
sortedArrayUsingDescriptors: または sortUsingDescriptors:メソッドを使うならば、 ソート記述子はそれらの属性のいくつかを使ってオブジェクトの集合のソートの簡単な方法を提供する。 多数の辞書があれば、あなたは名字、それから名前によってその内容を分類することができる。
リスト 5 は配列の生成の仕方および記述によるソートの方法を示す。
リスト 5 辞書配列の生成とソート

// キー名の定義
#define LAST    @"lastName"
#define FIRST   @"firstName"
//
// 辞書配列の生成
NSDictionary *dict; 
NSMutableArray *array = [NSMutableArray array]; 
//
dict = [NSDictionary dictionaryWithObjectsAndKeys: @"Jo"    , FIRST, @"Smith" , LAST, nil]; 
[array addObject:dict]; 
dict = [NSDictionary dictionaryWithObjectsAndKeys: @"Joe"   , FIRST, @"Smith" , LAST, nil]; 
[array addObject:dict]; 
dict = [NSDictionary dictionaryWithObjectsAndKeys: @"Joe"   , FIRST, @"Smythe", LAST, nil]; 
[array addObject:dict]; 
dict = [NSDictionary dictionaryWithObjectsAndKeys: @"Joanne", FIRST, @"Smith" , LAST, nil]; 
[array addObject:dict]; 
dict = [NSDictionary dictionaryWithObjectsAndKeys: @"Robert", FIRST, @"Jones" , LAST, nil]; 
[array addObject:dict]; 
//
// 姓/名それぞれでソートするためのNSSortDescriptorを作成する
NSSortDescriptor *lastDescriptor  = 
    [[[NSSortDescriptor alloc] initWithKey:LAST  ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)] autorelease]; 
NSSortDescriptor *firstDescriptor = 
    [[[NSSortDescriptor alloc] initWithKey:FIRST ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)] autorelease]; 

// 配列内容を姓/名順にソートする
NSArray *descriptors = [NSArray arrayWithObjects:lastDescriptor, firstDescriptor, nil]; // ソート順に入れる
NSArray *sortedArray = [array sortedArrayUsingDescriptors:descriptors]; 
リスト6に示すように、名前~姓順のソート順を変更することも簡単である。
NSSortDescriptorのascendingはソートの昇順=YES/降順=NOで設定する。
リスト 6 名前~姓順のソート

NSSortDescriptor *lastDescriptor  =
    [[[NSSortDescriptor alloc] initWithKey:LAST  ascending:NO selector:@selector(localizedCaseInsensitiveCompare:)] autorelease]; 
NSSortDescriptor *firstDescriptor =
    [[[NSSortDescriptor alloc] initWithKey:FIRST ascending:NO selector:@selector(localizedCaseInsensitiveCompare:)] autorelease]; 

NSArray *descriptors = [NSArray arrayWithObjects:firstDescriptor, lastDescriptor, nil]; // ソート順に入れる
NSArray *sortedArray = [array sortedArrayUsingDescriptors:descriptors]; 


対照的に、リスト7は関数を使用するソートの例である。 この方法は柔軟でない。
リスト 7 関数を用いたソート;柔軟でない
NSInteger lastNameFirstNameSort(id person1, id person2, void *reverse) 
{ 
    NSString *name1 = [person1 valueForKey:LAST]; 
    NSString *name2 = [person2 valueForKey:LAST]; 
    NSComparisonResult comparison = [name1 localizedCaseInsensitiveCompare:name2]; 
    if (comparison == NSOrderedSame) { 
        name1 = [person1 valueForKey:FIRST]; 
        name2 = [person2 valueForKey:FIRST]; 
        comparison = [name1 localizedCaseInsensitiveCompare:name2]; 
    } 
    if (*(BOOL *)reverse == YES) { 
        return 0 - comparison; 
    } 
    return comparison; 
}
BOOL reverseSort = YES; 
NSArray *sortedArray = [array sortedArrayUsingFunction:lastNameFirstNameSort context:&reverseSort]; 
ブロックによるソート
独自のソート基準に基づいて配列をソートするためには、ブロック構文を使うことができる。 sortedArrayUsingComparator: メソッドは、オブジェクトの比較のためにブロックを使って、NSArrayで新規の配列の中にソートする。 NSMutableArrayの sortUsingComparator: はオブジェクトの比較のためにブロックを使って、配列中でソートする。
リスト 8 ブロックによる配列の独自ソート

NSArray *sortedArray = [配列 sortedArrayUsingComparator:
    ^(id obj1, id obj2)
    { 
        if ([obj1 integerValue] > [obj2 integerValue]) { 
            return (NSComparisonResult)NSOrderedDescending; 
        } 
        if ([obj1 integerValue] < [obj2 integerValue]) { 
            return (NSComparisonResult)NSOrderedAscending; 
        } 
        // 一致
        return (NSComparisonResult)NSOrderedSame; 
    }
]; 
関数とセレクターによるソート
次は以下の3つのメソッドによるソート仕方である。
  1. sortedArrayUsingSelector:
  2. sortedArrayUsingFunction:context:
  3. sortedArrayUsingFunction:context:hint:
この中で(3)は少し難解である。
次のような配列があるとする。 ソート済み配列に対し、わずかな追加変更が行われた後再ソートを行うときは、 (3)のhint付きソートが最も効率的である。
N個の古い項目とP個の新しい項目の間のソートを概念的に合併することで、最初のソート後の情報を再利用することができる。 適切なヒントを得るために、最初のソートした時情報をsortedArrayHintで得る。 そして、それを必要となる時まで=修正された配列を再ソートする時まで、保持し続けておく。
リスト 9 セレクターと関数によるソート

NSInteger alphabeticSort(id string1, id string2, void *reverse) 
{ 
    if (*(BOOL *)reverse == YES) { 
        return [string2 localizedCaseInsensitiveCompare:string1]; 
    } 
    return [string1 localizedCaseInsensitiveCompare:string2]; 
}

// 注: anArray はソート済み 
NSMutableArray *anArray = [NSMutableArray arrayWithObjects:
            @"aa", @"ab", @"ac", @"ad", @"ae", @"af", @"ag", 
            @"ah", @"ai", @"aj", @"ak", @"al", @"am", @"an", @"ao", @"ap", @"aq", @"ar", @"as", @"at", 
            @"au", @"av", @"aw", @"ax", @"ay", @"az", @"ba", @"bb", @"bc", @"bd", @"bf", @"bg", @"bh", 
            @"bi", @"bj", @"bk", @"bl", @"bm", @"bn", @"bo", @"bp", @"bq", @"br", @"bs", @"bt", @"bu", 
            @"bv", @"bw", @"bx", @"by", @"bz", @"ca", @"cb", @"cc", @"cd", @"ce", @"cf", @"cg", @"ch", 
            @"ci", @"cj", @"ck", @"cl", @"cm", @"cn", @"co", @"cp", @"cq", @"cr", @"cs", @"ct", @"cu", 
            @"cv", @"cw", @"cx", @"cy", @"cz", nil]; 

NSData *sortedArrayHint = [anArray sortedArrayHint]; // ソート済み配列の情報を得ておく
[anArray insertObject:@"be" atIndex:5]; // 追加する

// セレクターによるソート
NSArray *sortedArray;
sortedArray = [anArray sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; 

// 関数によるソート
BOOL reverseSort = NO; 
sortedArray = [anArray sortedArrayUsingFunction:alphabeticSort context:&reverseSort]; 

// ヒントによるソート
sortedArray = [anArray sortedArrayUsingFunction:alphabeticSort context:&reverseSort hint:sortedArrayHint]; 
重要:
ローカライズが必要な文字列をソートするときは、ソート関数における比較に考慮が必要である。 別の言語または文脈では、ソートされた配列の正しい順序は、異なることがありえる。

配列のふるいがけ
NSArray とNSMutableArray クラスは配列要素のふるいがけのメソッドを提供する。
NSArray のfilteredArrayUsingPredicate:は、レシーバーの中に指定された属性と一致するオブジェクトを含んでいる新しい配列を返す。
NSMutableArray は filterUsingPredicate:も追加され、 指定された属性に対してレシーバーの内容を評価して、合わないオブジェクトだけを取り除く。

Predicateで指定するフォーマットについてはここ参照
ちょっとだけ説明。 これらのメソッドをリスト 10に示す。
リスト 10 属性による配列のふるいがけ

NSMutableArray *array = [NSMutableArray arrayWithObjects:@"Bill", @"Ben", @"Chris", @"Melissa", nil]; 


NSPredicate *bPredicate = [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"]; // 'b'または'B'で始まる物
NSArray *beginWithB     = [array filteredArrayUsingPredicate:bPredicate]; 
// beginWithB は { @"Bill", @"Ben" }を含む


NSPredicate *sPredicate = [NSPredicate predicateWithFormat:@"SELF contains[c] 's'"];  // 's'または'S'を含むも物
[array filterUsingPredicate:sPredicate]; 
// arrayは { @"Chris", @"Melissa" } を含む
NSIndexSet オブジェクトを使っても配列のふるいがけは出来る。 NSArray は objectsAtIndexes:を提供し、これはインデックスセットが提供する要素番号群のオブジェクトを含む新しい配列を返す。 NSMutableArray には removeObjectsAtIndexes:が追加され、これはインデックスセットを使って配列内でふるいがけをする。

NSArray/NSMuttableArrayのパフォーマンス情報:
辞書: キーと値の集合
辞書は、キーと値のペアを管理する。
辞書内におけるキーと値のペアは「エントリー」と呼ばれる。 それぞれのエントリは、1つ目がキーを示すオブジェクトで、2つ目のオブジェクトがキー値である。 辞書の中では、キーの値は固有(一意的)である。 すなわち、キーは1つの辞書内では同じものは存在しない(isEqual:によって決定される)。
キーは、NSCopyingプロトコルを採用して、ハッシュとisEqual:メソッドを実装していれば、どんなオブジェクトでもいい。

図4は、仮想的な人に関する情報を含む辞書を表す。 図の通り、辞書に含まれる値は、どんなオブジェクトでもよい(別の集合でも)。

図4 辞書


辞書の基礎
NSDictionaryは不変の辞書を管理する。 すなわち、新しい辞書を作ったら、キーと値の追加・削除・置換は出来ない。 個々の値の変更は、変更がサポートされているなら可能である。 しかし、キーは変更できない。

NSMutableDictionaryは可変辞書を管理する。 それは自動的に必要なメモリを確保して、いつでもエントリの追加・削除を許す。 可変辞書を使わなければならないのは、辞書が1つずつ変化するか、初期化により多くの時間かかる大きな集合のときである。

イニシャライザー initWithDictionary:または、コンビニエンスコンストラクタ dictionaryWithDictionary:を使って、 他の辞書から辞書インスタンスを簡単に生成できる。 たとえば、NSDictionaryのmyDictionaryインスタンスがあるなら、以下のように可変のコピーを作ることが出来る。
    NSMutableDictionary *myMutableDictionary = [NSMutableDictionary dictionaryWithDictionary: myDictionary]; 
一般にNSDictionary または NSMutableDictionary クラスに1つの辞書にメッセージを送ることで辞書をインスタンス化する。 辞書~メッセージは引数として渡すキーと値を含む辞書を返す。 辞書に追加する値のオブジェクトは(initWithDictionary:copyItems:でYESを指定しない限り)深くコピーされない。
オブジェクトは追加されるときにretainメッセージを受け取る。 辞書がreleseされるとき、各々の要素にもreleaseメッセージが送られる。
この基本的な考え方は、NSMutableArrayと同じである。

内部的には、辞書はその保存にハッシュテーブルを構築し、一致するキーへの値への高速アクセスを提供している。 が、ユーザーはそれを意識する必要は無い。 そのハッシュされた型ではなく、メソッドは直接キーをとる。

可変辞書
可変辞書からエントリを削除するとき、 エントリを構成するキーと値オブジェクトはreleaseメッセージを受信する。 他からのretainがないなら、そのオブジェクトは解放される。
もしrelease後もそのオブジェクトにアクセスしたいならば、その前にオブジェクトに対してretainを送っておく必要がある。 たとえば、リスト 1の第3行は、もしaDictionary が anObjectの唯一の所有者なら、 ランタイムエラーになる。
リスト 1 削除後のオブジェクトへのアクセスは危険

id anObject = [aDictionary objectForKey:theKey]; 
[aDictionary removeObjectForKey:theKey];  // theKeyを持つオブジェクトにreleaseが発行される
[anObject someMessage]; // クラッシュするかも
この可能性を回避するために、リスト2で例示されるように、それを取り除く前にオブジェクトを保持する。
リスト 2 削除する前にオブジェクトの所有権を保持する

id anObject = [[aDictionary objectForKey:theKey] retain]; // さらにもう1段保持を掛ける
[aDictionary removeObjectForKey:theKey]; 
[anObject someMessage]; 
// 使い終わった後に、anObjectにreleaseメッセージを送ること
可変辞書にオブジェクトを追加することは比較的直接である。
1組のキーと値を加える、または特定のキーのためにオブジェクトを置換するためには、 setObject:forKey: インスタンスメソッドをリスト 3の様に使う。
リスト 3 辞書にオブジェクトを追加する

#define LAST    @"lastName" 
#define FIRST   @"firstName"
NSMutableDictionary *dict=[NSMutableDictionary dictionaryWithObjectsAndKeys: @"Jo", FIRST, @"Smith", LAST, nil]; 

#define MIDDLE  @"middleInitial"
[dict setObject: @"M" forKey:MIDDLE]; 
他の辞書からエントリーを追加するにはaddEntriesFromDictionary: インスタンスメソッドを使うことも出来る。
もし両方の辞書が同じキーを持つなら、レシーバーにある前の同じキーのオブジェクトは解放され、 新しいオブジェクトがその場所を取る。 たとえば、リスト 4 を実行した後にはdictはキー"lastName"(=LAST)に値“Jones”を持つ。
リスト 4 他の辞書からエントリを追加する

#define LAST    @"lastName"
#define FIRST   @"firstName"
#define SUFFIX  @"suffix"
#define TITLE   @"title"
//                                                                           値        キー
NSMutableDictionary *dict=[NSMutableDictionary dictionaryWithObjectsAndKeys: @"Jo"   , FIRST ,
                                                                             @"Smith", LAST  ,  // ←これが
                                                                             nil]; 
NSDictionary     *newDict=[NSDictionary        dictionaryWithObjectsAndKeys: @"Jones", LAST  ,  // ←これで置換される
                                                                             @"Hon." , TITLE ,
                                                                             @"J.D." , SUFFIX,
                                                                             nil]; 
[dict addEntriesFromDictionary: newDict]; 

辞書のソート
NSDictionary はkeysSortedByValueUsingSelector: メソッドを提供する。 辞書がその値によってソートされるならば、その順序で辞書のキーの配列を返す。
リスト 5 値による辞書キーのソート

NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: 
//                                                           値          キー
                                            [NSNumber numberWithInt:63], @"Mathematics", 
                                            [NSNumber numberWithInt:72], @"English", 
                                            [NSNumber numberWithInt:55], @"History", 
                                            [NSNumber numberWithInt:49], @"Geography",
                                             nil]; 
NSArray *sortedKeysArray = [dict keysSortedByValueUsingSelector:@selector(compare:)]; // compare:は比較関数
sortedKeysArray は Geography, History, Mathematics, English の順になる。

ブロック構文を使うと、一致する値に基づいて辞書のキーをソートすることを容易にする。 そのためにはkeysSortedByValueUsingComparator: を使う。
リスト 6 ブロック構文は辞書のカスタムソートを容易にする

NSArray *blockSortedKeys = [dict keysSortedByValueUsingComparator:
    ^(id obj1, id obj2)
    { 
        if ([obj1 integerValue] > [obj2 integerValue]) { 
            return (NSComparisonResult)NSOrderedDescending; 
        } 
        if ([obj1 integerValue] < [obj2 integerValue]) { 
            return (NSComparisonResult)NSOrderedAscending; 
        } 
        return (NSComparisonResult)NSOrderedSame; 
    }
];

NSDictionary/NSMuttableDictionaryのパフォーマンス情報:
セット: 順序立てられてないオブジェクトの集合
セットはオブジェクトの順序立てられてない集合である。
要素の順序が重要でなく、オブジェクトのテストのパフォーマンスが重要なとき、 セットは配列に代わる物として使うことが出来る。 配列はソート済みであったとしても、その要素テストは、セットのそれより遅い。

図5 セット



セットの基礎
NSSetは、異なったオブジェクトの不変のセットを管理する。 つまり、セットを生成した後、オブジェクトの追加・削除・置換が出来ない。 しかし、修正がサポートされるなら、個々のオブジェクトの修正は出来る。

NSMutableSetはNSSetの子クラスで、自動的に必要に応じて記憶を割り当てて、 いつでもエントリの追加・削除を許す異なったオブジェクトの可変セットである。 セットが1つずつ代わるなら、または、初期化により多くの時間がかかる大きさの集合であるなら、可変セットを使わなければならない。

NSCountedSetはNSMutableSetの子クラスで、1回以上同じオブジェクトを加えることができる可変セットである。 しかし、同じオブジェクトが来た場合は、回数だけカウントして実体は再登録しない。 ゆえに、countedセットの中には、同じオブジェクトのインスタンスは1つしかない。 countForObject: メソッドはこのセットに特定のオブジェクトが何回追加されたかを返す。

このcountedセットはバッグ(bag)として知られている。 セットは、各々の異なったオブジェクトと関連したカウンターを持つ。 NSCountedSetオブジェクトは、オブジェクトが挿入される回数の経過を追って、 オブジェクトが完全にセットからオブジェクトを取り除くために同じ回数取り除かれることを義務づける。

可変オブジェクトをNSCountedSetに格納するなら、セット中にいる間変更してはいけない。

NSSetは setWithObjects: と initWithArray: といういくつかのイニシャライザーメソッドを提供する。 それは引数として渡している要素を含むNSSetのオブジェクトを返す。 (initWithSet:copyItems:でYESしない限り)オブジェクトは、セットに深くコピーされないで追加される。
オブジェクトは追加されるときにretainメッセージを受け取る。 セットがreleseされるとき、各々の要素にもreleaseメッセージが送られる。
この基本的な考え方は、NSMutableArrayと同じである。

セットは、NSCountedSetを除いて、 以下の状態に好ましい集合である。
可変セット
NSMutableSet は NSSetから提供されるいくつかのイニシャライザで生成することが出来る。
NSMutableSetクラスはセットへのオブジェクトの追加・削除のためのメソッドを提供する。
initWithSet:
setWithSet:
他のセットからインスタンスを生成する
addObject:セットへの単一オブジェクトの追加
addObjectsFromArray:指定配列から全オブジェクトの追加
unionSet:他のセットから、レシーバーに存在しないすべてのオブジェクトを加える
intersectSet:他のセット内に存在しない全てのオブジェクトを削除する
removeAllObjectsセットから全てのオブジェクトを削除する
removeObject:セットから特定のオブジェクトを削除する
minusSet:他のセット内にあるオブジェクトを全て削除する
NSCountedSet は NSMutableSetの子クラスなので、これらのメソッドの全てを受け継ぐ。 しかし、それらの一部はNSCountedSetでは異なる挙動をする。 たとえば
unionSet:それらがすでに存在するとしても、他のセットから全てのオブジェクトを追加する
intersectSet:他のセットにない全てのオブジェクトを削除する。
同じオブジェクトの複数のインスタンスがどちらのセットにでもあるならば、
結果として出来るセットはより少ない回数のインスタンスのオブジェクトを含む。
minusSet 他のセットにある全てのオブジェクトを削除する。
あるオブジェクトが1回以上counted セットに現れるなら、
それは、それの1つのインスタンスを取り除くだけである。
可変セットからエントリを削除するとき、 エントリを構成するオブジェクトはreleaseメッセージを受信する。 他からのretainがないなら、そのオブジェクトは解放される。
もしrelease後もそのオブジェクトにアクセスしたいならば、その前にオブジェクトに対してretainを送っておく必要がある。 たとえば、リスト 1の第3行は、もしaSet が anObjectの唯一の所有者なら、 ランタイムエラーになる。
リスト 1 オブジェクト削除後のアクセスは危険

id anObject = [aSet anyObject]; 
[aSet removeObject:anObject];  // anObjectにreleaseを発行する
[anObject someMessage]; // クラッシュするかも
この可能性を回避するために、リスト2で例示されるように、それを取り除く前にオブジェクトを保持する。
リスト 2 削除する前にオブジェクトの所有権を保持する
id anObject = [[aSet anyObject] retain]; // さらにもう1段保持を掛ける
[aSet removeObject:anObject]; 
[anObject someMessage]; 
// 使い終わった後に、anObjectにreleaseメッセージを送ること
セットの利用
NSSetクラスは、セットの要素を問い合わせるためのメソッドを提供する。
allObjectsセット中の全オブジェクトを含む配列を返す
anyObjectセット中のいくつかのオブジェクトを返す(オブジェクトはランダムではなく都合の良い物が選ばれる)
count現在のセット内のオブジェクト数を返す
member:セット内で指定オブジェクトに一致するオブジェクトを返す
intersectsSet:2つのセットが少なくとも1つのオブジェクトを共有するかどうか調べる
isEqualToSet:2つのセットが等しいかどうか調べる
isSubsetOfSet:セットに含まれるオブジェクトの全てがもう一セットでも存在するかどうか調べる
NSSetのメソッド objectEnumeratorは、セットの要素一つずつへの連続的なアクセスを提供する。 そして、themakeObjectsPerformSelector: と makeObjectsPerformSelector:withObject: メソッドは 個々のオブジェクトにメッセージを送ることの準備をする。

NSSet/NSMuttableSetのパフォーマンス情報:
インデックスセット: 配列に要素番号を格納すること
インデックスセットは、NSArray オブジェクトのような、他のデータ構造内の要素番号を格納する。 インデックスセット中にある要素番号は重複しない。 従って、インデックスセットは整数の任意の集合を保存することにふさわしくない。 図6のように、インデックスセットは要素番号を格納するために範囲を利用するので、 例えば配列において、それらは通常、整数値の集合を個別に保存するより効率的である。

図6 インデックスセットと配列の関係


インデックスセットの基礎
NSIndexSetは不変の要素番号群を管理する。 つまり、インデックスセットを作ったあとは、要素番号の追加・削除が出来ない。 NSMutableIndexSetは可変要素番号群を管理する。 それは、自動的に必要に応じて記憶を割り当てて、いつでも要素番号の追加・削除を許す。

イニシャライザー initWithIndexSet:を使うことで、他のインデックスセットからインスタンスを作ることができる。 可変のインデックスセットから不変のインデックスセットを作る場合、以下のようにする。
    NSIndexSet *myImmutableIndexes=[[NSIndexSet alloc]initWithIndexSet: myIndexes]; 
インデックスセットは、initWithIndex:またはinitWithIndexesInRange:によって、1つの要素番号または要素番号の範囲から初期化することが出来る。

可変インデックスセット
NSMutableIndexSet クラスのメソッドは、要素番号群または範囲の追加と削除を許す。 たとえば、共通の要素をもたない要素番号群の格納と必要に応じた既存の要素番号群の修正ができる。 これらのメソッドのいくつかは以下の通りである: もし空のNSMutableIndexSet オブジェクト myDisjointIndexesを持つなら、 リスト1のように、それを要素番号: 1, 2, 5, 6, 7, 10, で埋めることが出来る。
リスト 1 可変インデックスセットへの要素番号群の追加

    [myDisjointIndexes addIndexesInRange: NSMakeRange(1,2)]; 
    [myDisjointIndexes addIndexesInRange: NSMakeRange(5,3)]; 
    [myDisjointIndexes addIndex: 10]; 

インデックスセットを介した繰り返し
インデックスセットによって要素番号を付けられたオブジェクトの全てにアクセスすることは、 興味がある要素番号だけを調べることができるので、より効率的である。
NSArrayオブジェクト anArrayとそれに対応する NSIndexSet オブジェクト anIndexSetを持つなら、 リスト 2に示すように、インデックスセットを介してアクセス出来る。
リスト 2 インデックスセットを介した前方アクセス

NSUInteger index=[anIndexSet firstIndex]; 

while(index != NSNotFound) { 
    NSLog(@" %@",[anArray objectAtIndex:index]); 
    index=[anIndexSet indexGreaterThanIndex:index]; 
}
時として、インデックスセットを後方からアクセスする必要があるかも知れない。 たとえば、NSMutableArrayオブジェクトから選択的にインデックスからオブジェクトを取り除きたい時など。 この時はリスト3のように後方から反復出来る。
リスト 3 インデックスセットを通した後方アクセス

NSUInteger index=[anIndexSet lastIndex]; 
while(index != NSNotFound) { 
    if ([[aMutableArray objectAtIndex:index] isEqualToString:@"G"]){ 
        [aMutableArray removeObjectAtIndex:index]; 
    } 
    index=[anIndexSet indexLessThanIndex:index]; 
}
インデックスセットによって参照されるオブジェクトを選択的に取り除きたい場合だけ、 上記の方法が使われなければならない。
インデックスセット中の全ての要素番号のオブジェクトを削除するときは、 removeObjectsAtIndexes: を代わりに使う。

インデックスセットとブロック構文
ブロック構文と共に使われるとき、インデックスセットは特に強力である。 ブロック構文は、いくらかのテストにパスする配列のメンバーを指名するインデックスセットを作ることができる。 たとえば、数値のソートされてない配列をもち、20より小さい数の要素番号のインデックスセットを作りたいとき、 リスト4のようにする。
リスト 4 ブロック構文を使って配列からインデックスセットを作成する

NSIndexSet *lessThan20=[someArray indexesOfObjectsPassingTest:
    ^(id obj, NSUInteger index, BOOL *stop)
    { 
        if ([obj isLessThan:[NSNumber numberWithInt:20]]){ 
            return YES; 
        } 
        return NO; 
    }
]; 
インデックスセットは配列のブロック列挙を使うことも出来る。 インデックスセットに含まれる配列のインデックスだけを列挙するためは enumerateObjectsAtIndexes:options:usingBlock: メソッドを使う。

あるいは、インデックスセット自体は enumerateIndexesUsingBlock: メソッドを使ったブロックで列挙することが出来る。 たとえば、要素番号が示すセット中のそれぞれのオブジェクトに対してタスクを実行することが出来る。
リスト5の場合のように、インデックスセットが使われる配列にあてはめるならば、 複数の配列のオブジェクトにアクセスすることさえできる。
リスト 5 複数の配列にアクセスするインデックスセットを列挙する

[anIndexSet enumerateIndexesUsingBlock:
    ^(NSUInteger idx, BOOL *stop)
    { 
        if ([[firstArray objectAtIndex:idx]isEqual:[secondArray objectAtIndex:idx]]){ 
            NSLog(@"Objects at %i Equal",idx); 
        } 
    }
]; 


インデックスパス:入れ子になった配列を通したパスを格納する
インデックスパスは入れ子になった配列を通したパスを格納できる。
そしてそれはより複雑な集合階層構造(例えば木)でオブジェクトを取り戻す(retrieve)のに用いられる。
たとえば図7は、仮想の会社の階層構造を示す配列の入れ子になった組を示す。

図7 入れ子になった配列とインデックスパス



インデックスパスの基礎
図7に示す仮想の会社の階層構造を考えた場合、ルートの配列はCEOのための単一のエントリを含む。
その下の配列はいろいろな副社長から構成されている。それぞれの副社長の下に、部長などの配列がある。
もしあなたが、Europeマーケティング・チームの上に特定の従業員の地位を格納したいならば、単純な要素番号では十分でない。 その代わりに、入れ子になった配列を通しての経路が必要である。 例えば、Bill T.は、インデックス経路0.0.1.1.2によって指定出来る。

インデックスパスを1つの要素番号またはCのNSIntegerの配列から作ることができる。 リスト1 は Bill T.へのインデックスパスの作り方を示している。
リスト 1 配列からインデックスパスを作成する 
    NSUInteger integerArray[]={0,0,1,1,2}; 
    NSIndexPath *aPath=[[NSIndexPath alloc]initWithIndexes:integerArray length:(sizeof(integerArray)/sizeof(NSUInteger))]; 

インデックスパスの利用
NSIndexPath はパスの中で要素を問い合わせるためのメソッドを提供する。 たとえば、indexAtPosition: はインデックスパス内の与えられた位置に格納された要素番号を返す。 また、新しいインデックスパスを、新しい要素番号または最終インデックスを削除することで作成することも出来る。

iOSでは、 UITableViewとそのデリゲートとデータ源はインデックスパスをその内容の管理とユーザーとの対話に使う。 これを手助けするために、UIKitは、より完全にテーブル・ビューの列とセクションをインデックスパスに取り込むために、 プログラミング・インターフェースをNSIndexPathに加える。
たとえば, tableView:didSelectRowAtIndexPath: デリゲートメソッドを使うことで、インデックスパスはユーザーの選択を指定する。


集合のコピー
オブジェクトのコピーには2つの種類がある。浅いコピー(shallow copies)と深いコピー(deep copies)である。
通常のコピーは浅いコピーで、それは、アドレスのみコピーし、実体は共用する。 深いコピーは、新しいオブジェクト実体をコピーして生成し、新しい集合に付け加える。 この違いを図8に示す。

図8 浅いコピーと深いコピー



浅いコピー
集合の浅いコピーを作る方法はたくさんある。 浅いコピーを作ったときは、オリジナル集合内のオブジェクトにはretainメッセージが送られ、 ポインターが新しい集合にコピーされる。 リスト 1 は浅いコピーを使った新しい集合の生成のいくつかを示す。
リスト 1 浅いコピーの作成
    NSArray     *shallowCopyArray=[someArray copyWithZone:nil]; 
    NSDictionary *shallowCopyDict=[[NSDictionary alloc]initWithDictionary:someDictionary copyItems:NO]; 

深いコピー
集合の深いコピーを作るには2つの方法がある。
集合のinitWithArray:copyItems: の第2引数を YESにする方法。 この方法で集合の深いコピーを作った場合、集合内のそれぞれのオブジェクトにはcopyWithZone: メッセージが送られる。 集合内のオブジェクトがNSCopying プロトコルを採用しているならば、オブジェクトは新しい集合に深いコピーされる。 それは、それからコピーされたオブジェクトのたった一人の所有者である。 オブジェクトが NSCopying プロトコルを採用しない場合、実行時エラーに終わる。
しかしながら、copyWithZone: は浅いコピーを提供する。 この種類のコピーは、1レベル深いコピーを生じることが出来るだけである。 1レベル深いコピーを必要とするだけであるならば、 リスト2の場合のように1つをはっきりと要求することができる。
リスト 2 深いコピーの作成

    NSArray *deepCopyArray=[[NSArray alloc]initWithArray:someArray copyItems:YES]; 

コピーと可変性
集合をコピーしたとき, 集合またはオブジェクトの可変性に影響が出る。 コピーのそれぞれのメソッドは、集合の任意の深さのオブジェクトの可変性に、わずかに異なる影響を与える。
copyWithZone:表面レベルの不変を作成する。すべてのより深いレベルは、それらが以前持った可変性を持つ。
initWithArray:copyItems:第2引数=NO は、クラスに与えられた表面レベルの可変性を持つ。
すべてのより深いレベルは、それらが以前持った可変性を持つ。
initWithArray:copyItems:第2引数=YESは次のレベルは不変で、より深いレベルは、それらが以前持った可変性を持つ。
圧縮・解凍した集合は以前持っていた全てのレベルの可変性を捨てる。


列挙:集合の要素を横断する
Cocoa は3つの主な集合の列挙方法を持つ。 高速列挙とブロック列挙、それにObjective-C 2.0で高速列挙によって取って代わられたけれども、列挙型NSEnumeratorクラスもある。

高速列挙
高速列挙は集合の内容を列挙する好ましいメソッドである。 なぜなら、それは以下のような利点を持つからである。 高速列挙の動作は集合のタイプによってわずかに変化する。 配列とセットは、その内容を列挙する。辞書はキーを列挙する。 NSIndexSet と NSIndexPathは高速列挙をサポートしない。 集合オブジェクトに高速列挙を使う方法をリスト 1に示す。
リスト 1 辞書における高速列挙の利用

    // 配列の場合
    for (NSString *要素 in someArray) { 
        NSLog(@"要素: %@", 要素); 
    }
    
    // 辞書の場合
    NSString *キー; 
    for (キー in someDictionary){ 
        NSLog(@"Key: %@, Value %@", キー, [someDictionary objectForKey: キー]); 
}

ブロック列挙の利用
NSArray, NSDictionary と NSSet はブロック構文を使った内容の列挙を許可する。 ブロック構文を使った列挙は、適切なメソッドを使い、使うブロックを指定する。 リスト 2 はNSArray オブジェクトにおけるブロック列挙の例である。
リスト 2 配列のブロック列挙

NSArray *anArray=[NSArray arrayWithObjects:@"A", @"B", @"D",@"M",nil]; 
NSString *string=@"c";
//
[anArray enumerateObjectsUsingBlock:
    ^(id obj, NSUInteger index, BOOL *stop)
    { 
        if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame){ 
            NSLog(@"Object Found: %@ at index: %i",obj, index); 
            *stop=YES; 
        } 
    }
]; 
NSSet オブジェクトに対してはリスト 3のようなコードにする。
リスト 3 セットに対するブロック列挙

NSSet *aSet=[NSSet setWithObjects: @"X", @"Y", @"Z", @"Pi", nil]; 
NSString *aString=@"z"; 
//
[aSet enumerateObjectsUsingBlock:
    ^(id obj, BOOL *stop)
    { 
        if ([obj localizedCaseInsensitiveCompare:aString]==NSOrderedSame){ 
            NSLog(@"Object Found: %@", obj); 
            *stop=YES; 
        } 
    }
]; 
NSArray 列挙においては、要素番号引数は、並列の列挙に役立つ。 この引数がない場合、要素番号にアクセスする唯一の方法は、能率の悪いindexOfObject:メソッドを使うことである。
停止引数はパフォーマンスに有用である。なぜなら、それはブロック内で決定される若干の状態も基づいた、 列挙の早い停止を許すからである。
他の集合に対するブロック列挙メソッドは、名前とブロックシグネチャーによってわずかに異なる。 メソッド定義についてはそれぞれのクラスを見なさい。

列挙の利用
NSEnumerator は他のオブジェクトの集合を列挙する、簡単な抽象的な子クラスである。
配列・セット・辞書のような集合オブジェクトは、その内容の列挙に特別なNSEnumeratorオブジェクトを提供する。 新しく作られたNSEnumeratorオブジェクトに繰り返しnextObjectを送る。 それはオリジナルの集合の中から次のオブジェクトを返す。 集合が空になったら、nilを返す。 空になった集合をリセットすることは出来ない。 集合を再び列挙するときは、新しい列挙子を生成しなければならない。

NSArray, NSSet, NSDictionary のような集合クラスは、集合の型にふさわしい列挙子を返すメソッドを含んでいる。 たとえば, NSArray はNSEnumerator オブジェクトを返す2つのメソッド objectEnumerator と reverseObjectEnumerator を持つ。
NSDictionary クラスも NSEnumerator オブジェクトを返すための keyEnumerator と objectEnumeratorの2つのメソッドを持つ。
これらのメソッドは NSDictionary オブジェクトをキーまたは値それぞれの内容を列挙する。

Objective-Cにおいては,NSEnumerator オブジェクトはその列挙中(カスタムの子クラスで違う実装をした場合を除き)、集合を保持する。 列挙中の可変集合の要素への削除・置換・追加は安全でない。 列挙中の集合を修正する必要があるなら、集合のコピーを作って、それに対して列挙するか、 共有する列挙している間正しい情報を集め、後で変化を適用する。 第2のパターンをリスト 4に示す。
リスト 4 辞書の列挙とオブジェクトの削除

NSMutableDictionary *myMutableDictionary = ... ; 
NSMutableArray *keysToDeleteArray = [NSMutableArray arrayWithCapacity:[myMutableDictionary count]]; 
NSString *aKey;
//
NSEnumerator *keyEnumerator = [myMutableDictionary keyEnumerator]; 
while (aKey = [keyEnumerator nextObject])  { 
    if ( キーまたは値のテスト基準 ) { 
        [keysToDeleteArray addObject:aKey]; 
    } 
}
// 変更を反映する
[myMutableDictionary removeObjectsForKeys:keysToDeleteArray]; 
そうだったのか。なんか、objectEnumeratorのループ中に書き換えた記述をしたことがあったような、無かったような。 ビューを配列から取り出してプロパティーを変更するとか。 アドレスさえ変更してなければ良いのか?