Proper way to save and retrieve URL bookmark data

I'm having difficult saving and, more importantly, retrieving URL bookmark data.

I'm following the docs here https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html#//apple_ref/doc/uid/TP40010672-CH3-SW10

The resulting file written to my app's sandbox is a "MacOS Alias file". which is what I expect as per the docs :

If you write the persistent bookmark data to disk using the writeBookmarkData:toURL:options:error: method of NSURL, what the system creates on disk is an alias file.

The result when performing my get call is always Error Domain=NSCocoaErrorDomain Code=259 "The file couldn’t be opened because it isn’t in the correct format." which leads me to believe I'm not initializing my Data object correctly before calling resolvingBookmarkData.

I'm also lead to believe my Data initialization is the problem as I use the same code but instead of writing data to disk with try URL.writeBookmarkData(bookmarkData, to: fileURL) I store the Data in UserDefaults. Retriving that data from UserDefaults succeeds.

Any help is appreciated. Thanks in advance.


 private func save(fileURL: URL, completion: ((URL?) -> ())?) {
        do {
            // create bookmark data
            let bookmarkData = try fileURL.bookmarkData(
                options: .suitableForBookmarkFile, // create fails without this
                includingResourceValuesForKeys: nil,
                relativeTo: nil
            )

            // save bookmark data

            try URL.writeBookmarkData(bookmarkData, to: fileURL)

            print("write bookmark data to: \(fileURL)")

            completion?(nil)

        } catch {
            print(error.localizedDescription)
        }
    }

    private func get(completion: ((URL?) -> ())?) {
        do {
            var bookmarkDataIsStale = false

            // get bookmark data

            let fileUrl = listFile()

            // output: 
            // read Data contents of: file:///Users/me/Library/Developer/CoreSimulator/Devices/E3302B79-7520-4731-BC42-14D848B7DABD/data/Containers/Data/Application/66E23F7C-D345-4E4F-9F93-6825721731CE/Documents/Recordings/recording.wav
            print("read Data contents of: \(fileUrl)")

            // no luck; results in 'Error Domain=NSCocoaErrorDomain Code=259 "The file couldn’t be opened because it isn’t in the correct format."'
            let bookmarkData = try Data(contentsOf: fileUrl)

            // no luck; results in 'Error Domain=NSCocoaErrorDomain Code=259 "The file couldn’t be opened because it isn’t in the correct format."'
            // let bookmarkData = try Data(contentsOf: fileUrl, options: .alwaysMapped)

            // no luck; results in 'Error Domain=NSCocoaErrorDomain Code=259 "The file couldn’t be opened because it isn’t in the correct format."'
            // let bookmarkData = FileManager.default.contents(atPath: fileUrl.path)!

            // get resolved url with bookmark data

            // debugging
            // output:
            //  resourceValues: URLResourceValues(_values: [__C.NSURLResourceKey(_rawValue: _NSURLPathKey): /Users/pouriaalmassi/Library/Developer/CoreSimulator/Devices/E3302B79-7520-4731-BC42-14D848B7DABD/data/Containers/Data/Application/66E23F7C-D345-4E4F-9F93-6825721731CE/Documents/Recordings/recording.wav, __C.NSURLResourceKey(_rawValue: NSURLCanonicalPathKey): /Users/pouriaalmassi/Library/Developer/CoreSimulator/Devices/E3302B79-7520-4731-BC42-14D848B7DABD/data/Containers/Data/Application/66E23F7C-D345-4E4F-9F93-6825721731CE/Documents/Recordings/recording.wav], _keys: Set([__C.NSURLResourceKey(_rawValue: NSURLCanonicalPathKey), __C.NSURLResourceKey(_rawValue: _NSURLPathKey)]))

            let resourceValues = try! fileUrl.resourceValues(
                forKeys: [
                    URLResourceKey.pathKey,
                    URLResourceKey.canonicalPathKey
                ]
            )
            print("resourceValues: \(resourceValues)")

            // on options
            // used `.withoutUI` as it follows Apple's docs. Linked below.
            let resolvedUrl = try URL(
                resolvingBookmarkData: bookmarkData,
                options: [.withoutUI, .withoutImplicitStartAccessing],
                relativeTo: nil,
                bookmarkDataIsStale: &bookmarkDataIsStale
            )

            // balance calls to start/stop accessing securely scoped resource
            let didStartAccessing = resolvedUrl.startAccessingSecurityScopedResource()
            defer { resolvedUrl.stopAccessingSecurityScopedResource() }

            print(resolvedUrl)

            if !bookmarkDataIsStale && didStartAccessing {
                print("bookmark is _not_ stale.")
                print("resolvedUrl: \(resolvedUrl)")
                completion?(resolvedUrl)
            } else {
                // resolve if bookmark is stale. untested.
                print("bookmark is stale. renewing.")
                let resolved = try resolvedUrl.bookmarkData()
                print(resolved.description)
            }
        } catch {
            print("Error: \(error)")
        }
    }


Is there a reason you need to create an alias file specifically? The only reason to do that, at least as far as I can think of, is that you expect the user to open the alias file in the Finder. If that’s not your goal, and you just want to store the bookmark for your own internal use, you don’t need to create an alias file. You can simply save the bytes somewhere convenient (UserDefaults, an SQLite database, a file, or whatever).

Share and Enjoy

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

Thank you for the response.

Is there a reason you need to create an alias file specifically?

No need that I'm aware of. Seems to be the result of writeBookmarkData

You can simply save the bytes somewhere ... a file ...

To this point I had hoped to do this with try URL.writeBookmarkData(bookmarkData, to: fileURL). It appears I'm misusing the API.


My intention is to store user created audio recordings. I'd like to store them to the Documents directory (with iCloud backups enabled) and be able to retrieve them between launches.

With the following FileManager.default.createFile(atPath: fileURL.path, contents: bookmarkData, attributes: nil) will I be able to successfully retrieve the data between app launches?

A bookmark is just a Data value. You can read and write it like you would any other Data value, using routines like init(contentsOf:) and write(to:options:).

Share and Enjoy

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

Proper way to save and retrieve URL bookmark data
 
 
Q