使用空值对 NSArray、NSComparator 块与 NSSortDescriptor 进行排序
Sorting NSArray, NSComparator block vs. NSSortDescriptor, with null values
我一直在使用基于块的方法对 NSArray
...进行排序...但是,我注意到一个与排序相关的错误,因此开始调查。
背景:我正在处理 NSArray
个 EKReminder
个对象,它们有一个 creationDate
属性。我想按 降序 creationDate
对提醒进行排序(最新提醒,第一个)。
这是我的上一个代码:
// NSArray* fetchedReminders... contents pulled from reminder calendars...
NSArray* sortedArray = [fetchedReminders sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSDate* first = [(EKReminder*)a creationDate];
NSDate* second = [(EKReminder*)b creationDate];
return [second compare:first];
}];
我相信该代码是正确的。但是,我最终在数据库中找到了一些以 null
作为创建日期的提醒。这引入了一个错误 - 结果排序不正确。 null
值既不在开头也不在结尾,而且似乎数组中有空值会扰乱这种比较方法,因为许多提醒都是乱序的。
NSSortDescriptor
因此,我尝试将基于块的方法换成 sortedArrayUsingDescriptors
。这是当前代码:
// NSArray* fetchedReminders... contents pulled from reminder calendars...
NSSortDescriptor* sortDescriptor;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"creationDate" ascending:NO];
NSArray* sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray* sortedArray = [fetchedReminders sortedArrayUsingDescriptors:sortDescriptors];
这有效。
(根据当前数据,101 个提醒,其中 6 个有 null
个创建日期,它们都放在最后。其他提醒的顺序正确)。
问题
首先,我的 sortedArrayUsingComparator
方法有什么问题吗?
如果不是,这些不同的方法是否会以不同方式处理 null
?
无论如何,如果您的数据中可能有 null
s,那么 NSSortDescriptor
方法是否成为首选方法?
您 运行 遇到的核心问题是您没有手动处理传递给 sortedArrayUsingComparator:
的块中的空值。根据调用块的值,first
可以是 nil
,second
可以是 nil
,或者两者都可以是 nil
.
发送到 nil
returns 的任何消息等价的 0
值(例如,返回 float
的方法,当发送到 nil
returns 0.0f
,发送到 nil
时返回 int
的方法 returns 0
,以及返回发送到 nil
的对象的方法 returns nil
)。这意味着您有以下情况:
[<non-nil> compare:<non-nil>]
(returns 有效值)
[<non-nil> compare:nil]
(未定义的行为,根据 https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDate_Class/#//apple_ref/occ/instm/NSDate/compare:)
[nil compare:<non-nil>]
(returns 0;调用 nil
)
[nil compare:nil]
(returns 0,调用于 nil
)
这意味着当对数组中的值进行调用时,返回了一些无意义的值(例如 [nil compare:[NSDate date]]
returns 0
,等效NSOrderedSame
,这显然不是真的),更不用说未定义调用返回的结果了。
实际上,这些无效值被排序到数组中的奇怪位置。如果您对任何一个值为 nil
时应该发生的事情有一些定义的行为,您将获得一致的行为。
以下代码使排序行为保持一致(并且应该为您提供与上面的排序描述符方法类似的行为):
NSArray* sortedArray = [fetchedReminders sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSDate* first = [(EKReminder*)a creationDate];
NSDate* second = [(EKReminder*)b creationDate];
if (!first && !second) {
// nils have the same relative ordering
return NSOrderedSame;
} else if (!first) {
// second is not nil; send first toward the end of the array
return NSOrderedDescending;
} else if (!second) {
// first is not nil; send second toward the end of the array
return NSOrderedAscending;
} else {
// Neither is nil; this is valid
return [second compare:first];
}
}];
我一直在使用基于块的方法对 NSArray
...进行排序...但是,我注意到一个与排序相关的错误,因此开始调查。
背景:我正在处理 NSArray
个 EKReminder
个对象,它们有一个 creationDate
属性。我想按 降序 creationDate
对提醒进行排序(最新提醒,第一个)。
这是我的上一个代码:
// NSArray* fetchedReminders... contents pulled from reminder calendars...
NSArray* sortedArray = [fetchedReminders sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSDate* first = [(EKReminder*)a creationDate];
NSDate* second = [(EKReminder*)b creationDate];
return [second compare:first];
}];
我相信该代码是正确的。但是,我最终在数据库中找到了一些以 null
作为创建日期的提醒。这引入了一个错误 - 结果排序不正确。 null
值既不在开头也不在结尾,而且似乎数组中有空值会扰乱这种比较方法,因为许多提醒都是乱序的。
NSSortDescriptor
因此,我尝试将基于块的方法换成 sortedArrayUsingDescriptors
。这是当前代码:
// NSArray* fetchedReminders... contents pulled from reminder calendars...
NSSortDescriptor* sortDescriptor;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"creationDate" ascending:NO];
NSArray* sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray* sortedArray = [fetchedReminders sortedArrayUsingDescriptors:sortDescriptors];
这有效。
(根据当前数据,101 个提醒,其中 6 个有 null
个创建日期,它们都放在最后。其他提醒的顺序正确)。
问题
首先,我的 sortedArrayUsingComparator
方法有什么问题吗?
如果不是,这些不同的方法是否会以不同方式处理 null
?
无论如何,如果您的数据中可能有 null
s,那么 NSSortDescriptor
方法是否成为首选方法?
您 运行 遇到的核心问题是您没有手动处理传递给 sortedArrayUsingComparator:
的块中的空值。根据调用块的值,first
可以是 nil
,second
可以是 nil
,或者两者都可以是 nil
.
发送到 nil
returns 的任何消息等价的 0
值(例如,返回 float
的方法,当发送到 nil
returns 0.0f
,发送到 nil
时返回 int
的方法 returns 0
,以及返回发送到 nil
的对象的方法 returns nil
)。这意味着您有以下情况:
[<non-nil> compare:<non-nil>]
(returns 有效值)[<non-nil> compare:nil]
(未定义的行为,根据 https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDate_Class/#//apple_ref/occ/instm/NSDate/compare:)[nil compare:<non-nil>]
(returns 0;调用nil
)[nil compare:nil]
(returns 0,调用于nil
)
这意味着当对数组中的值进行调用时,返回了一些无意义的值(例如 [nil compare:[NSDate date]]
returns 0
,等效NSOrderedSame
,这显然不是真的),更不用说未定义调用返回的结果了。
实际上,这些无效值被排序到数组中的奇怪位置。如果您对任何一个值为 nil
时应该发生的事情有一些定义的行为,您将获得一致的行为。
以下代码使排序行为保持一致(并且应该为您提供与上面的排序描述符方法类似的行为):
NSArray* sortedArray = [fetchedReminders sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSDate* first = [(EKReminder*)a creationDate];
NSDate* second = [(EKReminder*)b creationDate];
if (!first && !second) {
// nils have the same relative ordering
return NSOrderedSame;
} else if (!first) {
// second is not nil; send first toward the end of the array
return NSOrderedDescending;
} else if (!second) {
// first is not nil; send second toward the end of the array
return NSOrderedAscending;
} else {
// Neither is nil; this is valid
return [second compare:first];
}
}];