[Cocoa] 배열의 원소를 정렬하는 방법 – NSSortDescriptor

정렬 디스크립터

원본 : https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/SortDescriptors/Articles/Creating.html#//apple_ref/doc/uid/20001845-BAJEAIEE

정렬 디스크립터(Sort Decriptor)는 집합 객체의 원소가 되는 객체들을 비교하는 규칙을 묘사하는 객체이다. NSSortDescriptor의 인스터는 슬 만들어서 어떤 키를 기준으로 정렬할 것인지, 올림차순으로 정렬할 것인지 내림차순으로 정렬할 것인지를 지정할 수 있다. 또한 기본 비교 메소드인 compare:외에 다른 비교 메소드를 사용하도록 지정할 수 있다.

정렬 디스크립터 자체가 정렬을 하지는 않는다는 것을 기억하자. 이 객체는 객체들이 어떻게 정렬되어야 하는지를 설명할 뿐이다. 실제 정렬은 NSArray나 NSMutableArray가 수행하게 된다.

NSSortDescriptor를 사용하여 정렬을 지정하기

커스텀 클래스인 Employee의 집합으로 구성된 배열이 하나 있다고 가정하자. Employee 객체는 firstNamelastName, 고용일과 나이정보를 프로퍼티로 제공하고 있다고 하자. 이 때 나이로 배열을 정렬해보자. 다음 예제는 NSSortDescriptor를 사용하여 나이의 오름차순으로 정렬된 배열을 얻는 방법을 설명한다.

NSSortDescriptor *ageDescriptor = [[NSSortDescriptor alloc] initWithKey:@"age" acending:YES];
NSArray *sortDescriptors = @[ageDescriptor];
NSArray *sortedArray = [employeesArray sortedArrayUsingDescriptors:sortDescriptors];

이 때 눈여겨 보아야 하는 부분은 하나의 디스크립터 인스턴스가 아니라 디스크립터들을 원소로 가지는 배열을 사용해야 한다는 점이다. 각각의 디스크립터는 순차적으로 적용되어 여러가지 키에 대해서 한 번에 정렬이 가능하다.

만약 고용일을 기준으로 정렬하고 싶다면 이 배열에 고용일을 기준으로 한 디스크립터를 추가하면 된다. 다음 예제는 한 번에 여러 가지의 기준을 적용하여 배열을 정렬하는 방법이다.

NSSortDescriptor *ageDescriptor = [[NSSortDescriptor alloc] initWithKey:@"age" ascending:YES];
NSSortDescriptor *hireDateDescriptor = [[NSSortDescriptor alloc] initWithKey:@"hireDate" ascending:YES];
NSArray *sortDescriptors = @[ageDescriptor, hireDateDescriptor];
NSArray *sortedArray = [employeesArray sortedArrayUsingDescriptors:sortDescriptors];

직원들의 이름을 기준으로 정렬를 하는 경우에는 조금 생각해봐야 하는 문제가 있다. 만약 직원 이름을 사용자 로케일에 따른 사전순으로 정렬하고 대소문자 구분을 하지 않고 싶다면 (NSString의 기본 compare:는 대소문자 구분을 한다.) 비교 메소드를 따로 지정해야 한다.

커스텀 비교 메소드 지정하기

앞서 살펴본 예제는 기본 compare:를 사용한다. 하지만 이름은 지역화된 문자열을 비교해야 하고 대소문자 구분을 하지 않아야 하므로 기본 compare:를 쓸 수 없다고 했다. 이 때는 localizedStandardCompare:를 사용해야 한다.

NSSortDescriptor *lastNameDesciptor = [[NSSortDescriptor alloc] initWithKey:@"lastName" ascending:YES selector:@selector(localizedStandardCompare:)];
NSSortDescriptor *firstNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"firstName" ascending:YES selector:@selector(localizedStandardCompare:)];
NSArray *sortDescriptors = @[lastNameDesciptor, firstNameDescriptor];
NSArray *sortedArray = [employeesArray sortedArrayUsingDescriptors:sortDescriptors];

다음 목록은 정렬 디스크립터를 사용하여 정렬할 수 있는 파운데이션 클래스들이다.

  • compare: – NSString, NSMutableString, NSDate, NSCalendarDate, NSValue, NSNumber
  • caseInsensitiveCompare: NSString, NSMutableString
  • localizedCompare: – NSString, NSMutableString
  • localizedCaseInsensitiveCompare: – NSString, NSMutableString
  • localizedStandardCompare: – NSString, NSMutableString

또한, 이 다음에 설명할 조건을 만족하는 메소드를 구현하면, 커스텀 메소드를 사용하여 정렬을 구현할 수 있다.

집합 객체의 요건

집합에 대해 NSSortDescriptor를 적용하여 정렬하려면 다음과 같은 조건을 만족해야 한다.

  • 각각의 객체는 정렬하고자 하는 키에 대해 키밸류 코딩으로 접근할 수 있어야 한다.
  • 집합내의 모든 객체는 지정한 키에 대해서 비교를 위한 메소드를 구현하고 있어야 한다. 커스텀 메소드가 지정되지 않았다면 기본 메서드를 사용해야 하는데 이 때 각 객체는 compare: 메소드를 기본적으로 구현해야 한다.
  • 비교 메소드는 단 하나의 파라미터만 받아, 그 파라미터와 객체 자신을 비교해서 적절한 NSComparisonResult값을 반환해야 한다.

NSComparisonResult 는 두 객체사이의 상대적인 순서를 의미하는 정수값으로 다음과 같이 선언되어 있다.

enum {
    NSOrderedAscending = -1,
    NSOrderedSame,
    NSOrderedDescending
};
typedef NSInteger NSComparisonResult;