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:

  1. 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)
        }
    }
    
  2. Add Products/relaunch binary to "Copy Bundle Resources" in the main application target.

  3. Add relaunch target to "Target Dependencies" in the main application target.

    screenshot

  4. 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)
    }
}

Tags:

Macos

Swift