Reputation: 3479
I cannot get a file reference URL to work in Swift. First, Swift's URL
type doesn't even define a method to convert a file path URL to a file reference URL. NSURL
has such a method: fileReferenceURL() -> URL?
, but I cannot figure out how to get it to work.
What I tried:
let myURL = URL(string: "file:///Users/Me/Desktop/CoolFolder")!
let refURL = (myURL as NSURL).fileReferenceURL()!
NSLog("\(myURL)") // file:///Users/Me/Desktop/CoolFolder (ok)
NSLog("\(refURL)") // file:///Users/Me/Desktop/CoolFolder (wha?!?)
logFolderContents() // Logs contents of folder pointed to by refURL
// ...
// Rename CoolFolder -> UncoolFolder in Finder.
// ...
logFolderContents() // Fails!
// ...
// Rename UncoolFolder -> CoolFolder.
// ...
logFolderContents() // Successfully logs contents again.
Basically, refURL
continues to point at the original file path. The call to fileReferenceURL()
seemingly did nothing.
How can I get a file reference URL in Swift?
Update: User @Sam points out in the comments that this appears to be a Swift 3 regression bug as documented here: https://bugs.swift.org/browse/SR-2728. Please visit the link and vote on this bug. The thread also contains a workaround.
Upvotes: 3
Views: 6100
Reputation: 12043
Sadly, the bug still is not fixed as of April 2021. This makes comparing file URLs for equality quite difficult.
With ObjC this works well:
NSString *p1 = @"/etc/hosts";
NSString *p2 = @"/private/etc/hosts";
NSURL *url1 = [NSURL fileURLWithPath:p1];
NSURL *url2 = [NSURL fileURLWithPath:p2];
NSURL *ref1 = url1.fileReferenceURL;
NSURL *ref2 = url2.fileReferenceURL;
BOOL equal = [ref1 isEqual:ref2];
In Swift, I got practically the same result (i.e. a 64 bit in containing both the volume ID and the file ID) with this code:
let p1 = "/etc/hosts"
let p2 = "/private/etc/hosts"
let url1 = URL(fileURLWithPath: p1)
let url2 = URL(fileURLWithPath: p2)
// This does not work because the refs do not use fileIDs as they should
// See https://bugs.swift.org/browse/SR-2728
let ref1bad = (url1 as NSURL).fileReferenceURL()!
let ref2bad = (url2 as NSURL).fileReferenceURL()!
let equalBad = ref1bad == ref2bad
print("\(ref1bad) = \(ref2bad): \(equalBad)")
// This works:
var ref1: NSObject?
var ref2: NSObject?
if let resourceValues = try? url1.resourceValues(forKeys: [.fileResourceIdentifierKey]) {
ref1 = resourceValues.fileResourceIdentifier as? NSObject
}
if let resourceValues = try? url2.resourceValues(forKeys: [.fileResourceIdentifierKey]) {
ref2 = resourceValues.fileResourceIdentifier as? NSObject
}
let equal = ref1!.isEqual(ref2) // the use of "!" is probably not good here
print("\(String(describing: ref1)) = \(String(describing: ref2)): \(equal)")
If you wanted a real file reference URL, you'd have to reconstruct the URL from the two 32 bit values in the object (which appears to be of type NSData
) and use the first as the fileID and the second as the volumeID. Writing the code for that exceeds my currently rather meager Swift skills, though.
Upvotes: 1