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
Dividing verbs, options, and other parameters up into the array of strings passed can be tricky
let fullPath = fileManager.homeDirectoryForCurrentUser.path + shortPath
represents the verb
var theListPar = ["list-keychains", "-d", "user"]
list-keychains
and its option -d user
Run a command with privileges using AppleScript
var theScript = "do shell script \"softwareupdate --schedule\" with administrator privileges"
then handle the result along the lines of:
let appleScript = NSAppleScript(source: theScript)
let eventResult = appleScript?.executeAndReturnError(nil)
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()