Reputation: 2030
I have long been using +[NSURL fileURLWithPath:]
because it's convenient, but in profiling I found that this was a source of bottlenecks because it queries the filesystem before calling down to +[NSURL fileURLWithPath:isDirectory:]
(or the Core Foundation equivalent):
This method assumes that
path
is a directory if it ends with a slash. Ifpath
does not end with a slash, the method examines the file system to determine ifpath
is a file or a directory. Ifpath
exists in the file system and is a directory, the method appends a trailing slash. Ifpath
does not exist in the file system, the method assumes that it represents a file and does not append a trailing slash.
I would like to avoid this overhead if possible. But I don’t always know ahead of time whether the URL I’m constructing is a directory. For example, I may just be given a path, or I may be given a directory and the name of an item (of unknown type) therein. My question is, is it OK to always pass NO
for isDirectory
even though this may turn out not to be accurate?
In particular, I want to make sure this doesn’t mess up -[NSURL getResourceValue:forKey:error:]
and NSFileManager
.
Some basic testing did not reveal any bad consequences to this optimization, but that doesn’t mean there aren’t any. It’s not fully clear to me why NSURL
cares about isDirectory
in the first place. Looking at the CFURL source, the code seems to try to make sure that directory paths always end with a slash. But, other than for display purposes, it’s not clear to me why this would matter for filesystem paths. (This was never an issue when using NSString
to represent paths.) The documentation for +[NSURL fileURLWithPath:isDirectory:]
says that isDir
is:
A Boolean value that specifies whether
path
is treated as a directory path when resolving against relative path components. PassYES
if the path indicates a directory,NO
otherwise.
Indeed, if I use -[NSURL initWithString:relativeToURL:]
, the new URL does not include the last component of baseURL
unless it was created with YES
for isDirectory
. However, I don’t think I’ve ever called this method, and it doesn’t seem like the system would need to do so on my behalf (for the above use cases). I note that -[NSURL URLByAppendingPathComponent:]
inserts a slash if necessary, rather than assuming that the IS_DIRECTORY
flag is correct. So, other than creating relative URLs, does this flag matter? Even if I wanted to, it does not seem possible in the general case to always pass in the correct value. The filesystem could always change out from under me. Nor can the system always determine it if I use +[NSURL fileURLWithPath:]
because the path might not yet exist in the filesystem.
Upvotes: 9
Views: 1336
Reputation: 4232
No, the isDirectory: parameter does not need to be correct, in my experience. Conversely, setting the isDirectory: parameter to YES, does not guarantee that url.path will end in a folder slash.
I've run a few tests on this and learned that it all depends on whether fileURLWithPath: can verify in the file system, and in a sandboxed application that may not always be possible.
That explains why this test in a sandboxed application has a perfect score, even though this is hardly the behaviour one would hope for:
- (void)testFileURLWithPathIsDirectory
{
NSString *path = NSHomeDirectory();
XCTAssertTrue([path isEqualToString:@"/Users/myUser"], @"%@", path);
NSURL *pathURL = [NSURL fileURLWithPath:NSHomeDirectory() isDirectory:YES];
XCTAssertTrue([pathURL.path isEqualToString:@"/Users/myUser"], @"%@", pathURL.path);
XCTAssertFalse([pathURL.path isEqualToString:@"file:///Users/myUser/"], @"%@", pathURL.path);
}
So apparently, isDirectory should be considered a hint, and developers should keep in mind that fileURLWithPath: and fileURLWithPath:isDirectory: will produce different paths depending on the sandboxing status of the URL.
One last gotcha: the path of an URL pointing at a package will never have a trailing slash, but if the URL points at a file within a package, that path will show that a package is just a directory.
Upvotes: 0
Reputation: 15003
If you pass in NO
and always create non-directory URLs, that's absolutely fine for any routines which use URLs to access the file system (e.g. -getResourceValue:forKey:error:
or NSFileManager
).
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!)
Upvotes: 1