Я пишу приложение для macOS на Swift, которому нужен привилегированный вспомогательный инструмент — хотелось бы, чтобы повышение прав не было необходимо, но похоже, что это так.
Я нашел это отличный пример приложения, специально предназначенного для этого сценария. Мне удалось перенести его код в свое собственное приложение, но я застрял на том этапе, когда мне нужно проверить, установлен ли вспомогательный инструмент, и если это не так, используйте SMJobBless()
и друзей для его установки.
При запуске примера приложения, если вспомогательный инструмент не установлен, приложение остается на следующем экране:
Чтобы было ясно, прочитав код, я подумал, что в какой-то момент предполагалось обновить метку до «Помощник установлен: нет», но, похоже, этого не происходит.
Если я нажму «Установить помощника», это результат.
С этого момента, если я не удалю вспомогательный инструмент вручную, при повторном запуске приложения будет отображаться этот экран с надписью «Помощник установлен: Да».
Такое поведение может быть приемлемым в данном примере ситуации, когда пользователю приходится вручную нажимать кнопку «Установить помощник». Однако в моем приложении я хотел бы, чтобы оно автоматически запрашивало установку вспомогательного инструмента, если он еще не установлен. Если он уже установлен, я не хочу тратить время пользователя на повторный запрос пароля.
Я думал, что это будет достаточно просто: если вспомогательный инструмент недоступен, где-то в процессе подключения к нему произойдет ошибка, которая побудит меня запросить установку инструмента. Если ошибок не возникает, предполагается, что инструмент уже установлен.
Вот взломанный код, который я написал для подключения к вспомогательному инструменту через XPC:
var helperConnection: NSXPCConnection?
var xpcErrorHandler: ((Error) -> Void)?
var helper: MyServiceProtocol?
// ...
helperConnection = NSXPCConnection(machServiceName: MyServiceName, options: .privileged)
helperConnection?.remoteObjectInterface = NSXPCInterface(with: MyServiceProtocol.self)
helperConnection?.resume()
helperConnection?.interruptionHandler = {
// Handle interruption
NSLog("interruptionHandler()")
}
helperConnection?.invalidationHandler = {
// Handle invalidation
NSLog("invalidationHandler()")
}
xpcErrorHandler = { error in
NSLog("xpcErrorHandler: \(error.localizedDescription)")
}
guard
let errorHandler = xpcErrorHandler,
let helperService = helperConnection?.remoteObjectProxyWithErrorHandler(errorHandler) as? MyServiceProtocol
else {
return
}
helper = helperService
Если вспомогательный инструмент не установлен, выполнение этого кода не приводит к ошибкам или выводу NSLog()
. Если после этого я вызову функцию через XPC (используя helper?.someFunction(...)
), ничего не произойдет — с таким же успехом я могу говорить с /dev/null
.
Теперь я ломаю голову в поисках способа определить, установлен ли инструмент. Решение проблемы в примерах приложений состоит в добавлении метода getVersion()
; если он что-то возвращает, «Установить помощник» становится серым, а метка меняется на «Помощник установлен: Да».
Я подумал о том, чтобы немного расширить эту идею, написав в своем инструменте простую функцию, которая мгновенно возвращается, и использую тайм-аут в основном приложении — если я не получу результат до истечения времени кода, вспомогательный инструмент, скорее всего, не установлены. Я нахожу это хакерским решением — что, если, например, вспомогательный инструмент (который запускается по требованию) запускается слишком долго, скажем, из-за того, что компьютер старый, а пользователь запускает что-то, интенсивно использующее ЦП?
Я вижу другие альтернативы, такие как просмотр файловой системы в ожидаемых местах (/Library/PrivilegedHelperTools
и /Library/LaunchDaemons
), но опять же это решение кажется мне неудовлетворительным.
Мой вопрос: Есть ли способ однозначно определить, прослушивает ли на другом конце привилегированный вспомогательный инструмент XPC?
Моя среда: macOS Mojave 10.14.2, Xcode 10.1, Swift 4.2.