Unordered Directory Contents

Since I updated to macOS 10.13 High Sierra, some of my unit tests broke. Examining the failures more carefully, I discovered that they were making assumptions about the order that Foundation’s FileManager.contentsOfDirectory(atPath:) would return items.

I wrote a quick playground to test the behavior on a 10.12 machine:

import Foundation

let array = try! FileManager.default.contentsOfDirectory(atPath: "/Applications/Utilities")

The results come back alphabetically ordered by file name:

[".DS_Store", ".localized", "Activity", "Adobe Flash Player Install", "AirPort", "Audio MIDI", "Bluetooth File", "Boot Camp", "ColorSync", "", "Digital Color", "Disk", "", "", "Keychain", "Migration", "Script", "System", "", "VoiceOver"]

The same playground on 10.13 tells a different story:

["AirPort", "VoiceOver", "", "Activity", ".DS_Store", "", "Audio MIDI", ".localized", "System", "Keychain", "", "Migration", "Script", "ColorSync", "", "Disk", "Bluetooth File", "Boot Camp", "Digital Color"]

I thought at first this might have been related to the APFS conversion that 10.13 applied to my boot volume, but the same ordering discrepancy occurs for items on my HFS+ volumes as well.

After checking the 10.13 release notes for clues, and finding none, I consulted the documentation. Well, what do you know?

The order of the files in the returned array is undefined.

So, mea culpa. The test code in question probably shouldn’t have ever made assumptions about the ordering of items returned from this method. While it has evidently always been undefined, it appears they are only making good on that promise in 10.13. You have been warned!

Update: It turns out I have some real bugs in my apps, not just in my tests, because of assuming the results of this call will be reasonably sorted. Luckily I use a bottleneck method for obtaining the list of files, and I can impose my own sorting right at the source. If you’re looking to make the same kinds of changes to your app, be sure to heed Peter Maurer’s advice and use “localizedStandardCompare” (available since macOS10.6/iOS4) to obtain Finder-like ordering of the results.