Restart application programmatically
Swift 3 version, based on Rintaro’s code and Cenox Kang’s workaround.
See Rintaro’s answer for instructions.
relaunch/main.swift:
import AppKit
// KVO helper
class Observer: NSObject {
let _callback: () -> Void
init(callback: @escaping () -> Void) {
_callback = callback
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
_callback()
}
}
// main
autoreleasepool {
// the application pid
guard let parentPID = Int32(CommandLine.arguments[1]) else {
fatalError("Relaunch: parentPID == nil.")
}
// get the application instance
if let app = NSRunningApplication(processIdentifier: parentPID) {
// application URL
let bundleURL = app.bundleURL!
// terminate() and wait terminated.
let listener = Observer { CFRunLoopStop(CFRunLoopGetCurrent()) }
app.addObserver(listener, forKeyPath: "isTerminated", context: nil)
app.terminate()
CFRunLoopRun() // wait KVO notification
app.removeObserver(listener, forKeyPath: "isTerminated", context: nil)
// relaunch
do {
try NSWorkspace.shared().launchApplication(at: bundleURL, configuration: [:])
} catch {
fatalError("Relaunch: NSWorkspace.shared().launchApplication failed.")
}
}
}
NSApplication+Relaunch.swift:
import AppKit
extension NSApplication {
func relaunch(sender: AnyObject?) {
let task = Process()
// helper tool path
task.launchPath = Bundle.main.path(forResource: "relaunch", ofType: nil)!
// self PID as a argument
task.arguments = [String(ProcessInfo.processInfo.processIdentifier)]
task.launch()
}
}
Yes, you need helper tool. here is the procedure:
Create helper "Command Line Tool" target in your Project. For example, named "relaunch"
relaunch/main.swift:
import AppKit // KVO helper class Observer: NSObject { let _callback: () -> Void init(callback: () -> Void) { _callback = callback } override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) { _callback() } } // main autoreleasepool { // the application pid let parentPID = atoi(C_ARGV[1]) // get the application instance if let app = NSRunningApplication(processIdentifier: parentPID) { // application URL let bundleURL = app.bundleURL! // terminate() and wait terminated. let listener = Observer { CFRunLoopStop(CFRunLoopGetCurrent()) } app.addObserver(listener, forKeyPath: "isTerminated", options: nil, context: nil) app.terminate() CFRunLoopRun() // wait KVO notification app.removeObserver(listener, forKeyPath: "isTerminated", context: nil) // relaunch NSWorkspace.sharedWorkspace().launchApplicationAtURL(bundleURL, options: nil, configuration: [:], error: nil) } }
Add
Products/relaunch
binary to "Copy Bundle Resources" in the main application target.Add
relaunch
target to "Target Dependencies" in the main application target.Add
relaunch
function in the main application.For example: NSApplication+Relaunch.swift:
extension NSApplication { func relaunch(sender: AnyObject?) { let task = NSTask() // helper tool path task.launchPath = NSBundle.mainBundle().pathForResource("relaunch", ofType: nil)! // self PID as a argument task.arguments = [String(NSProcessInfo.processInfo().processIdentifier)] task.launch() } }
Then, call NSApplication.sharedApplication().relaunch(nil)
as you like.
https://gist.github.com/BenLeggiero/449fb9b1a45b69fb276f4f9ad86cab7a
worked for me
func relaunch(afterDelay seconds: TimeInterval = 0.5) -> Never {
let task = Process()
task.launchPath = "/bin/sh"
task.arguments = ["-c", "sleep \(seconds); open \"\(Bundle.main.bundlePath)\""]
task.launch()
NSApp.terminate(self)
exit(0)
}
swift4 function
@objc private func buttonClicked(_ sender: NSButton) {
if let path = Bundle.main.resourceURL?.deletingLastPathComponent().deletingLastPathComponent().absoluteString {
NSLog("restart \(path)")
_ = Process.launchedProcess(launchPath: "/usr/bin/open", arguments: [path])
NSApp.terminate(self)
}
}