もっとNSArayソート

5746 ワード

http://blog.ablepear.com/2011/12/objective-c-tuesdays-more-nsarray.html
前回はC配列とNSAray配列について話しましたが、今日はNSSortDescriptorsを使ってNSAray順序を説明し続けます。
NSArayは、比較関数を指定して並べ替えを完了することを要求しています。いくつかの簡単なタイプのNSString、NSDateのNSAray順序付けの場合、比較関数は簡単に作成され、いくつかの一般的なオブジェクトは比較関数があります。
複雑なタイプのNSArayのために並べ替えた場合、比較関数の作成はより面倒で間違いやすい。これはPersonタイプのインターフェースである。

// Person.h
@interface Person : NSObject

@property (strong) Address *address;
@property (strong) NSDate *birthdate;
@property (copy) NSString *firstName;
@property (copy) NSString *lastName;

@end
これはPersonが使用するAddressタイプのインターフェースです。

// Address.h
@interface Address : NSObject

@property (copy, nonatomic) NSString *street;
@property (copy, nonatomic) NSString *city;
@property (copy, nonatomic) NSString *state;
@property (copy, nonatomic) NSString *country;
@property (copy, nonatomic) NSString *postalCode;

@end
私たちはPersonのNSArayを持っています。country、lastName、first Nameによって並べ替えます。これは比較blockの実現方法です。

// sort Person objects by lastName, firstName
Person *frodo = [Person new];
[frodo setFirstName:@"Frodo"];
[frodo setLastName:@"Baggins"];
// ...
[[frodo address] setCountry:@"Shire"];

Person *bilbo = [Person new];
[bilbo setFirstName:@"Bilbo"];
[bilbo setLastName:@"Baggins"];
// ...
[[bilbo address] setCountry:@"Shire"];

Person *legolas = [Person new];
[legolas setFirstName:@"Legolas"];
[legolas setLastName:@"Greenleaf"];
// ...
[[legolas address] setCountry:@"Mirkwood"];

NSArray *people = [NSArray arrayWithObjects:frodo, bilbo, legolas, nil];
NSArray *sortedPeople = [people sortedArrayUsingComparator:^(id item1, id item2) {
  Person *person1 = item1;
  Person *person2 = item2;
  
  // NSComparisonResult is a typedef for int
  NSComparisonResult result = [[[person1 address] country] compare:[[person2 address] lastName]];
  if (result) {
    return result;
  }
  
  result = [[person1 lastName] compare:[person2 lastName]];
  if (result) {
    return result;
  }
  
  result = [[person1 firstName] compare:[person2 firstName]];
  if (result) {
    return result;
  }
  
  return NSOrderedSame; // NSOrderedSame == 0
}];
// sortedPeople contains:
// Legolas Greenleaf (Mirkwood)
// Bilbo Baggins (Shire)
// Frodo Baggins (Shire)
複数フィールド比較の汎用モードは簡単で、各フィールドを順次チェックします。比較結果が0でない場合、停止して比較結果を返します。すべてのフィールドが等しい場合は、0を返します。(または、より記述的なNSOrderedSameを使って)、子孫のオブジェクトに深く入り込んでフィールドを確認する必要がある場合は、非常に複雑になります。
幸いなことに、この問題を簡単に解決する方法があります。NSArayには、NSSortDescriptorオブジェクトのセットがパラメータとして受信されます。各NSSortDescriptorオブジェクトは、keyパスとソート方向(昇順または降順)を指定します。配列中のNSSortDescriptorsの順に各フィールドの優先度を決定します。KVCに慣れていないなら、keyパスに遭遇したことがないかもしれません。KVCは文字型のフィールド名でゲットできます。setオブジェクトフィールドにアクセスします。子孫対象のフィールドにアクセスするには、使用が必要です。分離されたkeyパスを使用します。KVCは面白いものが多いです。しかし、今日はNSSortDescriptorsのセットを構築するだけです。

NSSortDescriptor *byCountry = [NSSortDescriptor sortDescriptorWithKey:@"address.country" 
                                                            ascending:YES];
NSSortDescriptor *byLastName = [NSSortDescriptor sortDescriptorWithKey:@"lastName" 
                                                             ascending:YES];
NSSortDescriptor *byFirstName = [NSSortDescriptor sortDescriptorWithKey:@"firstName" 
                                                              ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:byCountry, byLastName, byFirstName, nil];
注意byCountry順序記述子はkeyパス@「address.com untry」を使います。最初にPersonオブジェクトのaddress属性値を取得し、その後addressのcountry属性値を取得します。Keyパスは任意の深さでもいいです。
並べ替えディスクリプタグループを使うのは簡単です。

NSArray *sortedPeople = [people sortedArrayUsingDescriptors:sortDescriptors];
// sortedPeople contains:
// Legolas Greenleaf (Mirkwood)
// Bilbo Baggins (Shire)
// Frodo Baggins (Shire)
これは複雑な並べ替え規則を作成することで容易になり、フィールドのデフォルト比較関数に制限されなくなります。比較関数にselectorを指定してもいいです。

// specify a method to call on the lastName object
NSSortDescriptor *byLastName = [NSSortDescriptor sortDescriptorWithKey:@"lastName" 
                                                             ascending:YES
                                                              selector:@selector(caseInsensitiveCompare:)];
あるいは、より専門的な比較のために、NSCompratorのblockを伝えることができます。

// sort descriptor using length of last name
NSSortDescriptor *byLastNameLength = [NSSortDescriptor sortDescriptorWithKey:@"lastName" 
                                                                   ascending:YES 
                                                                  comparator:^(id item1, id item2) {
  NSString *lastName1 = item1;
  NSString *lastName2 = item2;
  // cast result to NSComparisonResult so that the 
  // compiler infers the correct return type
  return (NSComparisonResult) ([lastName1 length] - [lastName2 length]);
}];
NSSortDescriptorsで複雑な並べ替え規則を指定すると、読みやすく、書き込みやすく、メンテナンスしやすい声明コードです。ほとんどの場合、NSSortDescriptorを使うべきです。自分で比較関数を作成するのではなく、NSSortDescriptorを使うべきです。