Inconsistent caseInsensitiveCompare behavior

(lldb) p [@"ΗΙzzz" caseInsensitiveCompare:@"ᾚabc"]
(long long) -1
(lldb) p [@"ᾚabc" caseInsensitiveCompare:@"ΗΙzzz"]
(long long) -1

Note the unicode char in the second string. The results can't be both -1, afaik, if one is -1 the other one should be +1.

This causes inconsistent indexing in a sorted array resulting in obscure crashes of my app.

Am I doing something wrong?

Tested on iOS 27 and macOS 26.6.

Tried switching to localizedCaseInsensitiveCompare, though that has similar issues

NSString *a = @"\u1F28\u0399abc"; // ἨΙabc
NSString *b = @"\u1F2E\u0399zzz"; // ἮΙzzz

[a localizedCaseInsensitiveCompare:b] == NSOrderedDescending; // YES
[b localizedCaseInsensitiveCompare:a] == NSOrderedDescending; // YES

NSString *c = @"\u0397\u0313\u0345abc"; // ᾘabc
NSString *d = @"\u1F28\u0399abc";       // ἨΙabc

[c localizedCaseInsensitiveCompare:d] == NSOrderedAscending; // YES
[d localizedCaseInsensitiveCompare:c] == NSOrderedSame;      // YES

Really getting confused here...

Feedback Assistant: FB24975737

Yeah, string ordering is hard in the general case. I think it’s reasonable to file a bug about the behaviour you’re seeing, so thanks for FB24975737. As to what you can do about it right now, I want to ask about this:

This causes inconsistent indexing in a sorted array resulting in obscure crashes of my app.

What are you creating this array for? Is it a temporary thing that only exists in memory and it used, say, to present a sort list of items to the user? Or are you using it for something more persistent? Or something that’s internal, so not being displayed to the user.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

What are you creating this array for? Is it a temporary thing that only exists in memory and it used, say, to present a sort list of items to the user? Or are you using it for something more persistent? Or something that’s internal, so not being displayed to the user.

After sorting, the elements are divided into sections and then displayed to the user. The section membership is unstable now which causes the UI framework to throw.

I think I found a workaround for now in this category on NSString:

- (NSComparisonResult)fb24975737LocalizedCaseInsensitiveCompare:(NSString *)other {
    NSLocale *locale = NSLocale.currentLocale;
    // Fold each entire string first, then collate without case-insensitive options.
    NSString *left = [self stringByFoldingWithOptions:NSCaseInsensitiveSearch locale:locale];
    NSString *right = [other stringByFoldingWithOptions:NSCaseInsensitiveSearch locale:locale];
    return [left compare:right options:0 range:NSMakeRange(0, left.length) locale:locale];
}

With this code I cannot reproduce the inconsistencies anymore. Looks like Foundation's combined case-insensitive comparison is trying to outsmart itself.

I think I'd have the exact same issue if my app was fed strings like this.

Also would be particularly scared of using

- (NSUInteger)indexOfObject:(ObjectType)obj inSortedRange:(NSRange)r options:(NSBinarySearchingOptions)opts usingComparator:(NSComparator) 
Inconsistent caseInsensitiveCompare behavior
 
 
Q