The Weirdness of NSURL’s isDirectory Flag
Cocoa Protip: Using
+[NSURL fileURLWithPath:]
hits the filesystem and can cause slowdowns. Use+[NSURL fileURLWithPath:isDirectory:]
instead. Same goes for-[NSURL URLByAppendingPathComponent:isDirectory:]
I don’t remember if it’s documented [It is. —Michael] but it’s true (observable in Instruments/sample). If you don’t tell it whether the thing is a directory, it will go find out, immediately.
I’m well aware of this because it’s caused significant performance issues for me when dealing with large numbers of files. However, it’s not clear to me why it works this way:
The main point seems to be to ensure that URLs that represent directories have a trailing slash, even if you didn’t specify this in the string when creating the URL.
But the trailing slash is not important when dealing with POSIX paths, which is what the lower levels of the OS use, anyway. Why go to the effort to enforce this on URLs when it will essentially be discarded?
If the slash status is correct, you can use
hasDirectoryPath
orCFURLHasDirectoryPath()
to query the directory status without hitting the file system. But this is not actually reliable because there’s no guarantee that the file system entity matches the URL. In fact, it might not even exist.Back in 2013, I asked Stack Overflow whether it ever matters that the
isDirectory
parameter be correct. The only response I got was from Mike Abdullah, who wrote:For
file
URLs, the trailing slash only really becomes important if you then want to resolve paths against the URL. i.e. this snippet:NSURL *baseURL = [NSURL fileURLWithPath:path isDirectory:YES]; NSURL *result = [NSURL URLWithString:@"relative/path" relativeToURL:baseURL];
will produce different results to this one:
NSURL *baseURL = [NSURL fileURLWithPath:path isDirectory:NO]; NSURL *result = [NSURL URLWithString:@"relative/path" relativeToURL:baseURL];
In practice, dealing with relative strings/paths for
file
URLs tends not to be all that common anyway. Remote URLs need it a lot more (depends what your app does of course!)Of course,
NSURL
does not hit the network when creating a remote URL. If dealing with relative URLs is only time the slash matters, you’d think that hitting the file system could be deferred until/unless you actually try to form a relative URL.I later learned of a single case of a pasteboard API that uses
CFURLHasDirectoryPath()
when it probably should be checking the file system instead.In practice, even with the slow file system check, some URLs will end up with the wrong value for
isDirectory
because the URL may be created before the file exists, or the file may change to a directory (or vice-versa) after the URL was created.- When creating a new URL or appending a path component, you can prevent the file system hit by specifying
isDirectory
, but there is no equivalent parameter when appending or deleting a path extension.
Previously:
- URL Path Retrieval Cheat Sheet
- NSURL Cannot Handle Unicode Strings
- NSURL Path Handling
- +[NSURL URLWithString:] Changed