题 如何使用自定义对象对NSMutableArray进行排序?


我想做的事情似乎很简单,但我在网上找不到任何答案。我有一个 NSMutableArray 对象,让我们说它们是'人'对象。我想排序 NSMutableArray by Person.birthDate是一个 NSDate

我认为这与这个方法有关:

NSArray *sortedArray = [drinkDetails sortedArrayUsingSelector:@selector(???)];

在Java中我会使我的对象实现Comparable,或者使用带有内联自定义比较器的Collections.sort ......你到底如何在Objective-C中做到这一点?


1175
2018-04-30 06:10


起源




答案:


比较方法

要么为对象实现compare方法:

- (NSComparisonResult)compare:(Person *)otherObject {
    return [self.birthDate compare:otherObject.birthDate];
}

NSArray *sortedArray = [drinkDetails sortedArrayUsingSelector:@selector(compare:)];

NSSortDescriptor(更好)

或者通常更好:

NSSortDescriptor *sortDescriptor;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"birthDate"
                                           ascending:YES];
NSArray *sortedArray = [drinkDetails sortedArrayUsingDescriptors:@[sortDescriptor]];

您可以通过向阵列添加多个键轻松地按多个键进行排序。也可以使用自定义比较器方法。看一下 文件

块(有光泽!)

从Mac OS X 10.6和iOS 4开始,还可以使用块进行排序:

NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
    NSDate *first = [(Person*)a birthDate];
    NSDate *second = [(Person*)b birthDate];
    return [first compare:second];
}];

性能

-compare: 一般而言,基于块的方法比使用方法快得多 NSSortDescriptor 因为后者依赖于KVC。的主要优点 NSSortDescriptor 方法是它提供了一种使用数据而不是代码来定义排序顺序的方法,这使得例如设置好所以用户可以排序 NSTableView 通过单击标题行。


2216
2018-04-30 06:24



第一个示例有一个错误:您将一个对象中的birthDate实例变量与另一个对象本身进行比较,而不是其birthDate变量。 - Martin Gjaldbaek
@Martin:谢谢!有趣的是,在我获得75票之前没有人注意到。 - Georg Schölly
因为这是可接受的答案,因此可能被大多数用户认为是确定的,所以添加第3个基于块的示例可能会有所帮助,以便用户也知道它也存在。 - jpswain
@ orange80:我试过了。我不再拥有Mac了,所以如果你能查看代码那就太好了。 - Georg Schölly
如果你有 NSMutableArray 我更喜欢使用这些方法 sortUsingDescriptors , sortUsingFunction 要么 sortUsingSelector。就数组是可变的而言,我通常不需要排序副本。 - Stephan


NSMutableArray 方法 sortUsingFunction:context: 

你需要设置一个 比较 带有两个对象的函数(类型 Person,因为你正在比较两个 Person 对象)和a 上下文 参数。

这两个对象只是实例 Person。第三个对象是一个字符串,例如@“生日”。

此函数返回一个 NSComparisonResult:它返回 NSOrderedAscending 如果 PersonA.birthDate < PersonB.birthDate。它会回来 NSOrderedDescending 如果 PersonA.birthDate > PersonB.birthDate。最后,它会回来 NSOrderedSame 如果 PersonA.birthDate == PersonB.birthDate

这是粗糙的伪代码;你需要充实一个日期对于另一个日期“更少”,“更多”或“相等”的意义(例如比较秒 - 自 - 纪元等):

NSComparisonResult compare(Person *firstPerson, Person *secondPerson, void *context) {
  if ([firstPerson birthDate] < [secondPerson birthDate])
    return NSOrderedAscending;
  else if ([firstPerson birthDate] > [secondPerson birthDate])
    return NSOrderedDescending;
  else 
    return NSOrderedSame;
}

如果你想要更紧凑的东西,你可以使用三元运算符:

NSComparisonResult compare(Person *firstPerson, Person *secondPerson, void *context) {
  return ([firstPerson birthDate] < [secondPerson birthDate]) ? NSOrderedAscending : ([firstPerson birthDate] > [secondPerson birthDate]) ? NSOrderedDescending : NSOrderedSame;
}

如果你这么做的话,内联也许可以加快这一点。


103
2018-03-24 22:15



使用sortUsingFunction:context:可能是最常用的方式,绝对是最难以理解的方式。 - Georg Schölly
“c-ish”方法有什么问题?它工作正常。 - Alex Reynolds
它并没有什么问题,但我认为现在有更好的选择。 - Georg Schölly
也许,但是我认为对于那些可能正在寻找类似于Java的抽象Comparator类的人来说,对于那些实现比较(类型为obj1,类型为obj2)的人来说,它的可读性并不高。 - Alex Reynolds
我理解你们有几个人正在寻找批评这个完美答案的任何理由,即使这种批评很少有技术价值。奇怪的。 - Alex Reynolds


我在iOS 4中使用块来完成此操作。 不得不将我的数组元素从id转换为我的类类型。 在这种情况下,它是一个名为Score的类,其中包含一个名为points的属性。

如果数组的元素不是正确的类型,你还需要决定该怎么做,对于这个例子,我刚刚返回 NSOrderedSame但是在我的代码中我虽然例外。

NSArray *sorted = [_scores sortedArrayUsingComparator:^(id obj1, id obj2){
    if ([obj1 isKindOfClass:[Score class]] && [obj2 isKindOfClass:[Score class]]) {
        Score *s1 = obj1;
        Score *s2 = obj2;

        if (s1.points > s2.points) {
            return (NSComparisonResult)NSOrderedAscending;
        } else if (s1.points < s2.points) {
            return (NSComparisonResult)NSOrderedDescending;
        }
    }

    // TODO: default is the same?
    return (NSComparisonResult)NSOrderedSame;
}];

return sorted;

PS:这是按降序排序。


58
2018-03-16 18:46



你实际上并不需要“(得分*)”投射,你可以做“得分* s1 = obj1;”因为id会在没有编译器警告的情况下高兴地投射到任何东西:-) - jpswain
对桔子80 downcasting 在弱变量之前不需要强制转换。 - thesummersign
谢谢你们,我已经从答案中删除了演员 - Chris
您应该将nil与not-nil一致地排在顶部或底部,因此默认的结束返回可能是 return ((!obj1 && !obj2) ? NSOrderedSame : (obj1 ? NSOrderedAscending : NSOrderedDescending)) - Scott Corscadden
嘿克里斯,我尝试了这个代码,我在我的程序中刷新了......我第一次做正确的工作,得到了降序输出..但是当我刷新。(执行相同的代码和相同的数据)它改变了顺序,它不是下降..说我在我的阵列中有4个对象,3 hv相同的数据,1是不同的。 - Nikesh K


从iOS 4开始,您还可以使用块进行排序。

对于这个特殊的例子,我假设你的数组中的对象有一个'position'方法,它返回一个 NSInteger

NSArray *arrayToSort = where ever you get the array from... ;
NSComparisonResult (^sortBlock)(id, id) = ^(id obj1, id obj2) 
{
    if ([obj1 position] > [obj2 position]) 
    { 
        return (NSComparisonResult)NSOrderedDescending;
    }
    if ([obj1 position] < [obj2 position]) 
    {
        return (NSComparisonResult)NSOrderedAscending;
    }
    return (NSComparisonResult)NSOrderedSame;
};
NSArray *sorted = [arrayToSort sortedArrayUsingComparator:sortBlock];

注意:“已排序”数组将自动释放。


28
2017-11-22 10:51





我尝试了所有,但这对我有用。在课堂上我有另一个名为“crimeScene“,并希望按”属性排序“crimeScene”。

这就像一个魅力:

NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:@"crimeScene.distance" ascending:YES];
[self.arrAnnotations sortUsingDescriptors:[NSArray arrayWithObject:sorter]];

24
2018-05-06 16:25





缺少一步 GeorgSchölly的第二个答案,但它工作正常。

NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"birthDate"
                                              ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptor:sortDescriptors];

19
2018-03-30 05:54



方法调用实际上是“sortedArrayUsingDescriptors:”,最后是's'。 - LucasTizma
谢谢,还没有看到那个'。 - Georg Schölly


NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"birthDate" ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptors:sortDescriptors];

谢谢,它工作得很好......


18
2018-04-30 06:20





你的 Person 例如,对象需要实现一个方法 compare: 这需要另一个 Person 对象,并返回 NSComparisonResult 根据2个对象之间的关系。

然后你会打电话 sortedArrayUsingSelector: 同 @selector(compare:) 应该这样做。

还有其他方法,但据我所知,没有Cocoa-equiv的 Comparable 接口。运用 sortedArrayUsingSelector: 这可能是最无痛的方式。


16
2017-11-09 13:47





iOS 4块将为您节省:)

featuresArray = [[unsortedFeaturesArray sortedArrayUsingComparator: ^(id a, id b)  
{
    DMSeatFeature *first = ( DMSeatFeature* ) a;
    DMSeatFeature *second = ( DMSeatFeature* ) b;

    if ( first.quality == second.quality )
        return NSOrderedSame;
    else
    {
        if ( eSeatQualityGreen  == m_seatQuality || eSeatQualityYellowGreen == m_seatQuality || eSeatQualityDefault  == m_seatQuality )
        {
            if ( first.quality < second.quality )
                return NSOrderedAscending;
            else
                return NSOrderedDescending;
        }
        else // eSeatQualityRed || eSeatQualityYellow
        {
            if ( first.quality > second.quality )
                return NSOrderedAscending;
            else
                return NSOrderedDescending;
        } 
    }
}] retain];

http://sokol8.blogspot.com/2011/04/sorting-nsarray-with-blocks.html 一点描述


8
2017-12-06 07:51





对于 NSMutableArray, 使用 sortUsingSelector 方法。它在不创建新实例的情况下对其进行排序。


7
2018-04-16 09:17



只是一个更新:我也在寻找能够对可变数组进行排序的东西,现在对于iOS 7中的所有“sortedArrayUsing”方法都有“sortUsing”等效方法。如 sortUsingComparator:。 - jmathew