swiftappkitnsfilemanagernspasteboardnsfilepromiseprovider

macOS crash with NSFilePromiseProvider


I am trying to use NSFilePromiseProvider to ‘copy’ a file to the macOS pasteboard, but it always crashes. I have a macOS project that has a text file added to the bundle, and I load the file from the bundle and confirm that it exists. Then I pass the URL to a function that tries this:

        let pasteboard = NSPasteboard.general
        pasteboard.clearContents()

        let declaredUTType =  (UTType(filenameExtension: fileURL.pathExtension)?.identifier)
                ?? "public.data"

        let promiseWriter = FilePromiseWriter(sourceURL: fileURL, fileName: fileURL.lastPathComponent)
        let provider = NSFilePromiseProvider(fileType: declaredUTType, delegate: promiseWriter)
        pasteboard.writeObjects([provider])

The NSFilePromiseProviderDelegate class looks this:

final class FilePromiseWriter: NSObject, NSFilePromiseProviderDelegate {
    private let sourceURL: URL
    private let fileName: String
    init(sourceURL: URL, fileName: String) {
        self.sourceURL = sourceURL
        self.fileName = fileName
    }
    
    func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, writePromiseTo url: URL) async throws {
        print("filePromiseProvider writePromiseTo url called - destinationURL: \(url)")
        assert(FileManager.default.fileExists(atPath: sourceURL.path))
        try FileManager.default.copyItem(at: sourceURL, to: url)
    }
    
    func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
        print("filePromiseProfile fileName called = \(fileName)")
        return fileName
    }
}

The crash comes from the pasteboard.writeObjects line. The crash comes from NSPasteboardItem.m:222

*** Assertion failure in -[NSPasteboardItem setString:forType:], NSPasteboardItem.m:222
Invalid parameter not satisfying: string
(
    0   CoreFoundation                      0x000000018af9aca0 __exceptionPreprocess + 176
    1   libobjc.A.dylib                     0x000000018aa5eb90 objc_exception_throw + 88
    2   Foundation                          0x000000018c5a9a78 -[NSCalendarDate initWithCoder:] + 0
    3   AppKit                              0x000000018f0d107c -[NSPasteboardItem setString:forType:] + 164
    4   AppKit                              0x000000018fa1d314 -[NSFilePromiseProvider _pasteboard:item:provideDataForType:completionHandler:] + 24
    5   AppKit                              0x000000018f71f958 __68-[NSPasteboard _setOwner:forTypes:atIndex:selector:usesPboardTypes:]_block_invoke + 728
    6   CoreFoundation                      0x000000018aff0c10 -[_CFPasteboardEntry resolvePromisedDataWithCompletionQueue:isLocalPromise:completionHandler:] + 556
    7   CoreFoundation                      0x000000018aff46f4 ___CFPasteboardHandleFulfillMessage_block_invoke_2 + 172
    8   CoreFoundation                      0x000000018af29874 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 28

....

    22  TestCopyFilePasteboardCrash.debug.d 0x000000010243a308 $s27TestCopyFilePasteboardCrash0abcdE3AppV5$mainyyFZ + 40
    23  TestCopyFilePasteboardCrash.debug.d 0x000000010243a3b4 __debug_main_executable_dylib_entry_point + 12
    24  dyld                                0x000000018aa9eb98 start + 6076
)
FAULT: NSInternalInconsistencyException: Invalid parameter not satisfying: string; {
    NSAssertFile = "NSPasteboardItem.m";
    NSAssertLine = 222;
}

Am I missing something, or doing something wrong here? The delete callback doesn't seem to be called at all (though I would expect it to be called when I 'paste', not when I 'copy'). I've tried with App Sandbox enabled and disabled but same results both times.


Solution

  • Although NSFilePromiseProvider conforms to NSPasteboardWriting you cannot add an instance of it to the pasteboard. NSFilePromiseProvider is used for drag and drop operations.

    To "copy" a file to the pasteboard you add its url -

    guard let url = Bundle.main.url(forResource: "somefile", withExtension: "txt") as? NSURL else {
        print("File not found")
        return
    }
    let pasteboard = NSPasteboard.general
    pasteboard.clearContents()
    pasteboard.writeObjects([url])
    

    After running this, if you hit ctrl-v in a Finder window you will get a copy of your file pasted into that location.