swiftswift2nsapplescript

Unexpectedly found nil while unwrapping an Optional value (AppleScript result)


I am trying to make a program in Swift 2 that runs and gets the result of an AppleScript script.

Here is my code:

import Foundation

func runAppleScript(script:String) -> String
{
    let errorInfo = AutoreleasingUnsafeMutablePointer<NSDictionary?>()
    let startAtLoginScript: NSAppleScript = NSAppleScript(source: script)!
    let theDiscriptor:NSAppleEventDescriptor = startAtLoginScript.executeAndReturnError(errorInfo)
    let theResult:String = theDiscriptor.stringValue! //This is whats causing the error

    return theResult
}

let scriptResult = runAppleScript("tell app \"Spotify\" to playpause")

NSLog("\(scriptResult)")

The problem is the program crashes and outputs:

fatal error: unexpectedly found nil while unwrapping an Optional value

in the console. I have also tried if let else, however that does not work either. How would I fix this issue?

This was tested using a OS X Command Line template using the swift language.


Solution

  • Actually the error could come from NSAppleScript(source: script)! so the proper solution is to return an Optional String and not use force unwrapping at all:

    func runAppleScript(script:String) -> String? {
        let errorInfo: AutoreleasingUnsafeMutablePointer<NSDictionary?> = nil
        let startAtLoginScript = NSAppleScript(source: script)
        let theDescriptor = startAtLoginScript?.executeAndReturnError(errorInfo)
        return theDescriptor?.stringValue
    }
    
    if let scriptResult = runAppleScript("tell app \"Spotify\" to playpause") {
        NSLog("\(scriptResult)")
    } else {
        print("the script execution failed")
    }
    

    If you prefer having a default value instead of nil when it fails, then no need to return an Optional:

    func runAppleScript(script:String) -> String {
        let errorInfo: AutoreleasingUnsafeMutablePointer<NSDictionary?> = nil
        let startAtLoginScript = NSAppleScript(source: script)
        let theDescriptor = startAtLoginScript?.executeAndReturnError(errorInfo)
        return theDescriptor?.stringValue ?? ""  // if nil, returns the default ""
    }
    
    let scriptResult = runAppleScript("tell app \"Spotify\" to playpause")
    NSLog("\(scriptResult)")
    

    As for using the new Swift 2 error handling system, none of the methods you're using inside runAppleScript are throwing errors, so it would only work if you used a custom error type and throw the errors yourself. Example:

    enum MyAppleScriptError: ErrorType {
        case ExecutingScriptFailed
        case GettingStringValueFailed
    }
    
    func runAppleScript(script:String) throws -> String {
        let errorInfo: AutoreleasingUnsafeMutablePointer<NSDictionary?> = nil
        let startAtLoginScript = NSAppleScript(source: script)
        guard let theDescriptor = startAtLoginScript?.executeAndReturnError(errorInfo) else {
            throw MyAppleScriptError.ExecutingScriptFailed
        }
        guard let value = theDescriptor.stringValue else {
            throw MyAppleScriptError.GettingStringValueFailed
        }
        return value
    }
    
    do {
        let scriptResult = try runAppleScript("tell app \"Spotify\" to playpause")
        NSLog("\(scriptResult)")
    } catch {
        print(error)
    }
    

    Swift 3

    Same idea, but some implementation details are different.

    func runAppleScript(_ script:String) -> String? {
        let errorInfo: AutoreleasingUnsafeMutablePointer<NSDictionary?>? = nil
        if let startAtLoginScript = NSAppleScript(source: script) {
            let theDescriptor = startAtLoginScript.executeAndReturnError(errorInfo)
            return theDescriptor.stringValue
        }
        return nil
    }
    
    if let scriptResult = runAppleScript("tell app \"Spotify\" to playpause") {
        NSLog("\(scriptResult)")
    } else {
        print("no return value")
    }
    

    And with error handling:

    enum MyAppleScriptError: ErrorProtocol {
        case ExecutingScriptFailed
        case GettingStringValueFailed
    }
    
    func runAppleScript(_ script:String) throws -> String {
        let errorInfo: AutoreleasingUnsafeMutablePointer<NSDictionary?>? = nil
        let startAtLoginScript = NSAppleScript(source: script)
        guard let theDescriptor = startAtLoginScript?.executeAndReturnError(errorInfo) else {
            throw MyAppleScriptError.ExecutingScriptFailed
        }
        guard let value = theDescriptor.stringValue else {
            throw MyAppleScriptError.GettingStringValueFailed
        }
        return value
    }
    
    do {
        let scriptResult = try runAppleScript("tell app \"Spotify\" to playpause")
        NSLog("\(scriptResult)")
    } catch {
        print(error)
    }