Feeling that I am at last making some progress, the time has come to factor out some of my previously experimental Swift code so that it is re-usable. At this stage, I’m not making any systematic attempt to build a library of AppleScript-like calls, but I want to clean up my code and make it simple to use in other Swift apps.
I have been reading up on ‘protocol-oriented’ programming, mainly using Jon Hoffman’s iBook Swift 3 Protocol-Oriented Programming, which is already in its second edition, published by Packt and available in the iBooks Store. Having spent quite a lot of coding time in the past with Common Lisp, I had not realised how relatively primitive are the object-oriented features in C-family languages. Still, Protocols seem to go some way towards addressing those shortcomings.
For the moment, though, I am not intending to extend much. A simple Swift source file of additional calls should suffice.
These now include:
func getBundleVersion(path: String) -> String
which returns the version number of a bundle
func doShellScript(launchPath: String, arguments: [String]?) -> [String]
which runs a shell command and returns its standard output or an error code
func doShellScriptWithPrivileges(source: String) -> String
which uses AppleScript to run a shell command with elevated privileges
func doErrorAlertSheet(message: String, window: NSWindow)
which runs a simple error alert as a sheet
func doErrorAlertModal(message: String)
which runs a simple modal error alert in a separate window.
The source for those is shown below, and derived from the code which has been working in my app.
For the current LockRattler source, this cleans the code up so that getting the version number of a bundle into its text field is a one-liner:
theXProtVerOut.stringValue = getBundleVersion(path: theXProtPath)
Similarly, running a shell command is now one line:
let status1 = doShellScript(launchPath: theSIPCmd, arguments: theSIPPar)
as is the more fraught and fudged running a command with privileges:
let eventResult = doShellScriptWithPrivileges(source: theASScript)
Handling an error in the File Save code can now be dealt with thus:
doErrorAlertSheet(message: error.localizedDescription, window: self.window)
So the business code now reads as below.
I’m pleased and impressed. For ease of use, I am appending the source to those functions below.
Then I am moving on to work through Erica Sadun’s invaluable guide to Swift’s documentation markup, so that my functions are all properly documented.
// SwiftScript.swift
import Foundation
import Cocoa
import Security
func getBundleVersion(path: String) -> String {
let theVerKey = "CFBundleShortVersionString"
let myBundle = Bundle.init(path: path)
let myVer = myBundle?.object(forInfoDictionaryKey: theVerKey)
return myVer as! String
}
func doShellScript(launchPath: String, arguments: [String]?) -> [String] {
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
let outPipe = Pipe()
task.standardOutput = outPipe
task.launch()
let fileHandle = outPipe.fileHandleForReading
let data = fileHandle.readDataToEndOfFile()
task.waitUntilExit()
let status = task.terminationStatus
if (status != 0) {
return ["", "Failed, error = " + String(status)]
}
else {
return [(NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String), "0"]
}
}
func doShellScriptWithPrivileges(source: String) -> String {
let appleScript = NSAppleScript(source: source)
let eventResult = appleScript?.executeAndReturnError(nil)
if (eventResult == nil) {
return "ERROR"
} else {
return (eventResult?.stringValue)!
}
}
func doErrorAlertSheet(message: String, window: NSWindow) {
let alert: NSAlert = NSAlert()
alert.messageText = "An error occurred in your last action:"
alert.informativeText = message
alert.alertStyle = NSAlertStyle.critical
alert.addButton(withTitle: "OK")
alert.beginSheetModal(for: window, completionHandler: nil)
}
func doErrorAlertModal(message: String) {
let alert: NSAlert = NSAlert()
alert.messageText = "An error occurred in your last action:"
alert.informativeText = message
alert.alertStyle = NSAlertStyle.critical
alert.addButton(withTitle: "OK")
alert.runModal()
}