Swift Snippets 5: Bundles, Process, Background activity, Shell commands

Bundles

Working with bundles.

Get version of a bundle:
var theKey = "CFBundleShortVersionString"
var bundlePath = "/System/Library/CoreServices/XProtect.bundle"
let myBundle = Bundle.init(path: bundlePath)
let theVersion = myBundle1?.object(forInfoDictionaryKey: theKey)

Process

Formerly NSTask, now Process.

Call a shell command simply;
let path = "usr/bin/say"
let params = ["arg1", "arg2"]
let task = Process.launchedProcess(launchPath: path, arguments: params)
task.waitUntilExit()

Call a shell command at path with arguments params, an array of strings, and retrieve standard output
let task = Process()
task.launchPath = path
task.arguments = params
let outPipe = Pipe()
task.standardOutput = outPipe
task.launch()
let fileHandle = outPipe.fileHandleForReading
let data = fileHandle.readDataToEndOfFile()
task.waitUntilExit()

For stderr, use
task.standardError = outPipe

Handle errors afterwards using
let status = task.terminationStatus
if status != 0 {
// handle errors
}

Convert the returned data to a string using
let theString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)!

Parameters are not passed through a command shell to expand abbreviations such as ~ for the Home folder. Each parameter must be complete and ready to hand to the command itself. To expand a path in the Home folder to a complete path
let fileManager = FileManager.default
let fullPath = fileManager.homeDirectoryForCurrentUser.path + shortPath
Dividing verbs, options, and other parameters up into the array of strings passed can be tricky
var theListPar = ["list-keychains", "-d", "user"]
represents the verb list-keychains and its option -d user

Run a command with privileges using AppleScript
var theScript = "do shell script \"softwareupdate --schedule\" with administrator privileges"
let appleScript = NSAppleScript(source: theScript)
let eventResult = appleScript?.executeAndReturnError(nil)
then handle the result along the lines of:
if (eventResult == nil) {
return "ERROR"
} else {
return (eventResult?.stringValue)!
}

When run, AppleScript displays the authentication dialog into which the user enters their admin password, and will then use that to obtain the privileges required to run the command.

Using NSPredicate
let pred = NSPredicate(format: "%K == 'com.apple.TimeMachine'", argumentArray: ["subsystem"])
let string = pred.predicateFormat

creates
subsystem == "com.apple.TimeMachine"
See documentation for format options.

Background activity

Running background and scheduled activities.

Repeated task run in background, scheduled by DAS and CTS
let activity = NSBackgroundActivityScheduler(identifier: "com.mycompany.myapp.check")
activity.repeats = true
activity.interval = 60 * 60
activity.schedule() { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
let task = Process()
task.launchPath = "/usr/local/bin/blowhole"
task.arguments = []
let outPipe = Pipe()
task.standardOutput = outPipe
task.launch()
task.waitUntilExit()
completion(NSBackgroundActivityScheduler.Result.finished)
}

Single task handed to the global DispatchQueue
DispatchQueue.global().async {
let task = Process()
task.launchPath = "/usr/local/bin/blowhole"
task.arguments = []
let outPipe = Pipe()
task.standardOutput = outPipe
task.launch()
task.waitUntilExit()
}

Repeating background task using XPC Activity
var criteria = xpc_dictionary_create(nil, nil, 0)
xpc_dictionary_set_bool(criteria, XPC_ACTIVITY_REPEATING, true)
xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_DELAY, XPC_ACTIVITY_INTERVAL_1_MIN)
xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_INTERVAL, XPC_ACTIVITY_INTERVAL_1_MIN)
xpc_dictionary_set_string(criteria, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY)
xpc_activity_register("com.mycompany.myapp.check", criteria) {activity in
let task = Process()
task.launchPath = "/usr/local/bin/blowhole"
task.arguments = []
let outPipe = Pipe()
task.standardOutput = outPipe
task.launch()
task.waitUntilExit()
}

A one-shot task handed to XPC Activity
var criteria = xpc_dictionary_create(nil, nil, 0)
xpc_dictionary_set_bool(criteria, XPC_ACTIVITY_REPEATING, false)
xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_DELAY, XPC_ACTIVITY_INTERVAL_1_MIN)
xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_GRACE_PERIOD, 3 * XPC_ACTIVITY_INTERVAL_1_MIN)
xpc_dictionary_set_string(criteria, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY)
xpc_activity_register("com.mycompany.myapp.check", criteria) {activity in
let task = Process()
task.launchPath = "/usr/local/bin/blowhole"
task.arguments = []
let outPipe = Pipe()
task.standardOutput = outPipe
task.launch()
task.waitUntilExit()
}

Shell commands

Handling parameters and options in shell commands.

Enum and function to parse options
enum OptionType: String {
case debug = "b"
case defaultD = "d"
case error = "e"
case fault = "f"
case help = "h"
case info = "i"
case unknown
init(value: String) {
switch value {
case "b": self = .debug
case "d": self = .defaultD
case "e": self = .error
case "f": self = .fault
case "h": self = .help
case "i": self = .info
default: self = .unknown
}
}
}

Print the usage summary
func printUsage() {
print("usage:")
print("unorml -[x] string")
print("to return the Unicode normalized version of the given string")
print("unorml -h to show usage information.")
}

func getOption(_ option: String) -> (option: OptionType, value: String) {
return (OptionType(value: option), option)
}

check argument count, parse first argument, and switch according to the option supplied
let argCount = CommandLine.argc
if (argCount > 2) {
let first = CommandLine.arguments[1]
let second = CommandLine.arguments[2]
let (option, value) = getOption(first.substring(from: first.characters.index(first.startIndex, offsetBy: 1)))
switch option {
case .debug:
doThis()
case .error:
doThat()
case .fault:
doTheOther()
case .info:
doAnother()
case .help:
printUsage()
case .defaultD, .unknown:
fputs("unorml: unknown option\n", stderr)
printUsage()
}
}
else {
printUsage()

Swift Snippets 0: Introduction and Contents