我正在用Swift编写一个macOS应用程序,它需要一个特权助手工具--希望升级不是必要的,而是看起来就像。
我发现这非常优秀的示例应用程序,特别是针对这个场景定制的。我已经成功地将它的代码移植到我自己的应用程序中,但是我不得不检查助手工具是否已经安装,如果没有安装,请使用SMJobBless()和朋友来安装它。
在运行示例应用程序时,如果未安装助手工具,则应用程序将停留在以下屏幕上:

要明确的是,从阅读代码开始,我以为它应该在某个时候将标签更新为“:No”,但这似乎并没有发生。
如果我单击"Install“,结果就是这样。

从现在开始,除非我手动删除助手工具,否则重新运行这个应用程序将显示这个屏幕上的"Helper Installed: Yes“。
在本例中,用户必须手动单击"Install“按钮时,这种行为可能是正常的。但是,在我的应用程序中,如果还没有安装,我希望它能够自动请求安装助手工具。如果它已经安装,我不想浪费用户的时间再次请求他们的密码。
我认为这很简单:如果助手工具不可用,在连接到它的过程中的某个地方会发生错误,这是我请求安装该工具的触发器。如果没有发生错误,则假定该工具已经安装。
下面是我为通过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()方法;如果它返回某项内容,“Installed”就会变成灰色,标签将更改为"Helper : Yes“。
我考虑通过在我的工具中编写一个简单的函数,立即返回,并在主应用程序中使用超时来扩展这个想法--如果在代码超时之前我没有得到结果,那么助手工具很可能不会安装。我发现这是一个很麻烦的解决方案--例如,如果助手工具(按需启动)要花费太长时间才能启动,比如因为计算机很旧,用户运行的是CPU密集型的东西?
我看到了其他的替代方案,比如在预期的位置查看文件系统(/Library/PrivilegedHelperTools和/Library/LaunchDaemons),但是这个解决方案仍然让我感到不满意。
我的问题是:是否有一种方法可以轻松地检测特权XPC助手工具是否正在另一端侦听?
我的环境: macOS Mojave 10.14.2,Xcode 10.1,SWIFT4.2。
发布于 2019-01-21 17:38:12
由于您创建了助手工具,只需添加一个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方法,这些方法是我在安装新助手后立即发送的,或者就在我计划卸载或替换安装的助手之前。
发布于 2019-01-19 14:02:09
我将检查文件系统是否存在二进制文件(in /Library/PrivilegedHelperTools,以及plist是否存在于/Library/LaunchDaemons中)。然后,您可以联系XPC服务,并调用某种ping函数,如果服务启动并运行,该函数将响应。
只是我的第二个cts
罗伯特
发布于 2020-08-19 08:03:31
背景
实际上,可以避免等待工具响应的超时。事实上,您引用的erikberglund/SwiftPrivilegedHelper示例确实会在"Helper“textfield旁边打印单词"No”,如果该工具没有启用(请参阅这里),当然是异步的,但实际上是瞬间的。
然而,我处于一个独特的位置,您在我自己的特权助手的实现中描述了这个问题,但是SwiftPrivilegedHelper示例完全适合我。当工具未安装时,在前者中从不调用remoteObjectProxyWithErrorHandler错误处理程序,而在后者中则调用。
因为我有一个关于你的问题的例子,也有一个有用的例子,我相信我已经找到了根本原因:
根本原因
至少在我的例子中,没有干净地卸载助手工具。
在某种程度上,我从/Library/LaunchDaemons中删除了它的/Library/LaunchDaemons,从/Library/PrivilegedHelperTools中删除了工具本身,但是我想我没有运行sudo launchctl unload /Library/LaunchDaemons/com.example.foo.plist
当我运行sudo launchctl list或sudo launchctl print system/com.example.foo.plist时,它仍然被列出。
在这种情况下,对该工具的调用显然不会成功(因为该工具没有安装),但不会失败(因为launchctl认为它是安装了的)。
解决方案
正确的卸载可以通过以下两种方式之一完成:
sudo launchctl remove com.example.foo (注:非system/com.example.foo)sudo launchctl unload /Library/LaunchDaemons/com.example.foo.plist (这要求磁盘上仍然存在plist )。如果正确卸载,sudo launchctl print system/ca.example.foo应打印:
Bad request.
Could not find service "com.example.foo" in domain for system奎因“爱斯基摩人”表示以下序列,它使用remove方法:
一旦我清理了东西,瞧,当工具没有安装时,我的remoteObjectProxyWithErrorHandler开始调用它的错误处理程序。
https://stackoverflow.com/questions/54264378
复制相似问题