我正在Swift中编写一个macOS应用程序,该应用程序需要一个特权的帮助程序工具-希望不是必需的,而是it looks like it is。
我发现了this出色的示例应用程序,该应用程序特别适合于这种情况。我已经设法将其代码移植到我自己的应用程序中,但是我被困在需要检查是否安装了辅助工具的地步,如果没有安装,请使用SMJobBless()
和朋友来安装它。
在运行示例应用程序时,如果未安装帮助程序工具,则该应用程序将停留在以下屏幕上:
要清楚,从阅读代码开始,我认为应该在某个时候将标签更新为“ Helper Installed:No”,但这似乎没有发生。
如果单击“安装助手”,则结果为
。从现在开始,除非我手动删除帮助程序工具,否则重新运行该应用程序将显示“安装了帮助程序:是”的屏幕。
在此示例情况下,用户必须手动单击“安装帮助器”按钮,此行为可能是正确的。但是,在我的应用程序中,我希望它自动请求安装辅助工具(如果尚未安装)。如果已经安装了,我不想浪费用户的时间来再次请求密码。
我认为这很简单:如果没有可用的辅助工具,则在连接该辅助工具的过程中会发生错误,这是触发我请求安装该工具的触发器。如果没有错误发生,则认为该工具已经安装。
这是我编写的用于通过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()
方法。如果返回内容,则“ Install Helper”将变灰,并且标签将更改为“ Helper Installed:Yes”。
我考虑过通过在我的工具中编写一个可以立即返回的简单函数并在主应用程序中使用超时来稍微扩展一下这个想法-如果直到代码超时才得到结果,则使用辅助工具可能未安装。我发现这是一个骇人听闻的解决方案-例如,如果辅助工具(按需启动)启动时间过长,又说是因为计算机陈旧且用户正在运行CPU密集型设备,该怎么办? >
我看到了其他替代方法,例如在预期的位置(/Library/PrivilegedHelperTools
和/Library/LaunchDaemons
)窥视文件系统,但是对于我来说,这种解决方案仍然让人感到不满意。
我的问题:有没有一种方法可以明确地检测特权XPC帮助程序工具是否正在监听另一端?
我的环境:macOS Mojave 10.14.2,Xcode 10.1,Swift 4.2。
答案 0 :(得分:2)
创建辅助工具后,只需添加一个XPC消息处理程序即可报告工具的状态。启动时,连接并发送该消息。如果任何失败,则说明您的工具未正确安装(或没有响应)。
在我的代码中,我所有的XPC服务(包括我的特权帮手)都采用了用于测试和操作安装的基本协议:
@protocol DDComponentInstalling /*<NSObject>*/
@required
- (void)queryBuildNumberWithReply:(void(^_Nonnull)(UInt32))reply;
@optional
- (void)didInstallComponent;
- (void)willUninstallComponent;
queryBuildNumberWithReply:
返回一个描述组件版本号的整数:
- (void)queryBuildNumberWithReply:(void(^)(UInt32))reply
{
reply(FULL_BUILD_VERSION);
}
如果消息成功,我将返回的值与应用程序中的内部版本号常量进行比较。如果它们不匹配,则该服务是较旧/较新的版本,需要替换。对于我的产品的每次公开发布,此常数都会递增。
我使用的代码如下:
- (BOOL)verifyServiceVersion
{
DDConnection* connection = self.serviceConnection;
id<DDComponentInstalling> proxy = connection.serviceProxy; // get the proxy (will connect, as needed)
if (proxy==nil)
// an XPC connection could not be established or the proxy object could not be obtained
return NO; // assume service is not installed
// Ask for the version number and wait for a response
NSConditionLock* barrierLock = [[NSConditionLock alloc] initWithCondition:NO];
__block UInt32 serviceVersion = UNKNOWN_BUILD_VERSION;
[proxy queryBuildNumberWithReply:^(UInt32 version) {
// Executes when service returns the build version
[barrierLock lock];
serviceVersion = version;
[barrierLock unlockWithCondition:YES]; // signal to foreground thead that query is finished
}];
// wait for the message to reply
[barrierLock lockWhenCondition:YES beforeDate:[NSDate dateWithTimeIntervalSinceNow:30.0];
BOOL answer = (serviceVersion==FULL_BUILD_VERSION); // YES means helper is installed, alive, and correct version
[barrierLock unlock];
return answer;
}
请注意,DDConnection
是XPC连接的实用程序包装,而barrierLock
技巧实际上是封装在共享方法中的-因此,我不会一遍又一遍地写这个了-但它是未包装的出于演示目的。
我还需要解决安装前/安装后/升级方面的问题,因此我的所有组件都实现了可选的didInstallComponent
和willUninstallComponent
方法,这些方法在安装新帮助程序后立即发送,或者只是在我计划卸载或替换已安装的帮助程序之前。
答案 1 :(得分:0)
我将检查文件系统是否存在二进制文件(在/ Library / PrivilegedHelperTools中,并且plist是否在/ Library / LaunchDaemons中)。然后,您可以联系XPC服务,并调用某种ping函数,该函数会回答该服务是否已启动并正在运行。
仅2克拉,
罗伯特
答案 2 :(得分:0)
实际上 可以避免等待工具响应的超时。实际上,您引用的erikberglund/SwiftPrivilegedHelper
示例确实在异步工具(实际上是瞬间)未启用的情况下,在“已安装Helper”文本字段旁边打印了“否”字样(请参见here)。>
但是,我处于独特的位置,可以完全按照您自己在特权帮助程序的实现中所描述的问题进行操作,但是SwiftPrivilegedHelper
示例完全可以为我工作。如果未安装该工具,则永远不会调用remoteObjectProxyWithErrorHandler
错误处理程序,而在后者中则会调用它。
因为我有一个有关您的问题的示例以及一个可行的示例,所以我相信我已经找到了根本原因:
至少就我而言,没有干净地卸载帮助程序工具。在某个时候,我从plist
中删除了它的/Library/LaunchDaemons
,从/Library/PrivilegedHelperTools
中删除了工具本身,但我想我还没有运行sudo launchctl unload x
。当我运行sudo launchctl list
时,它仍然被列出。在这种情况下,对该工具的调用显然不会成功(因为未安装该工具),但不会失败(因为launchctl认为它已安装)。
我一目了然地(通过运行sudo launchctl remove x
)清除了所有内容,而我的remoteObjectProxyWithErrorHandler
在未安装该工具时开始调用其错误处理程序。