I'm trying to implement a "file copy with progress" in Swift on macOS. After lots of searching I just found rustle's implement in Objective-C. It works pretty fine. But I would like it "swifty". I tried it with some simplified code:
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var copyfileCallback: copyfile_callback_t = {(what, stage, state, sourcePath, destPath, context) -> Int32 in
return COPYFILE_CONTINUE
}
@IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let src = NSURL(fileURLWithPath: "Source_File_Path").fileSystemRepresentation
let dst = NSURL(fileURLWithPath: "Destination_File_Path").fileSystemRepresentation
let flag: copyfile_flags_t = UInt32(COPYFILE_ALL)
let state = copyfile_state_alloc()
// If I implement this, the copyfile() method will complain "EXC_BAD_ACCESS(code=2..." error
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), ©fileCallback)
copyfile(src, dst, state, flag)
}
}
The basic function copyfile()
works fine. But if I implement the callback function by providing a pointer of copyfileCallback
closure to copyfile_state_set()
, then copyfile()
just complains "Bad_Access...".
I guess maybe the closure was released before the C api tries to access it.
But I have no idea how to solve this problem...
Any clue will be so appreciated.
The error is here:
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), ©fileCallback)
because is passes the address of the copyfileCallback
variable to the function, not the function pointer itself. In C you can pass an arbitrary function as a void *
argument. In Swift you have to cast the function to a pointer explicitly:
let state = copyfile_state_alloc()
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB),
unsafeBitCast(copyfileCallback, to: UnsafeRawPointer.self))
And don't forget to release the memory eventually, after the copy operation:
copyfile_state_free(state)
Remark: In Swift it is recommended to use the (value overlay type) URL
instead of NSURL
:
let srcURL = URL(fileURLWithPath: "Source_File_Path")
let destURL = URL(fileURLWithPath: "Destination_File_Path")
let result = srcURL.withUnsafeFileSystemRepresentation { srcFile in
destURL.withUnsafeFileSystemRepresentation { destFile in
copyfile(srcFile, destFile, state, flag)
}
}