在 macOS 中以编程方式启用、禁用和启动服务

Enable, disable and start services programmatically in macOS

我正在编写一个带有服务的程序。到目前为止,我所做的是创建一个辅助工具,它可以 运行 为我的流程提升任务并可以通过 XPC 进行通信。

我的程序捆绑了一个服务,我想使用帮助工具来安装和start/stop这个服务,这样我的程序可以在设置中有一个复选框"start service with system"。

我可以成功复制该服务的 plist,但我找不到任何方法以编程方式启用、禁用、启动或停止该服务。我认为调用 system("launchctl load /path/to/service.plist"); 的解决方案非常难看。 objective C 中是否有任何机制来完成此任务并获得成功或失败的结果?

Apple 已弃用 C API,用于在 launch.h 中启动、停止和启用已启动的服务。 API 的源代码在他们的开源站点上:https://opensource.apple.com/source/launchd/launchd-442.26.2/liblaunch/

下面是一些要求 launchd 启动 LittleSnitchUIAgent 服务的示例代码:

#include <launch.h>

int main(int argc, const char * argv[]) {
    const char *job = "at.obdev.LittleSnitchUIAgent";
    launch_data_t resp, msg;
    msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
    launch_data_dict_insert(
        msg, launch_data_new_string(job), LAUNCH_KEY_STARTJOB);
    resp = launch_msg(msg);
    launch_data_free(msg);
    return 0;
}

LittleSnitchUIAgent 并不重要——我是从本地服务列表中随机选择的。我没有检查示例中的错误,以保持直截了当。

如果您还没有,我建议您仔细研究 launchd man pages and the Daemons and Services Programming Guide。 Launchd 可以启动您的进程以响应几乎任何事情:计时器、套接字连接、添加到系统的设备等等。您实际上很少需要管理自己的服务。我无法证实这一点,但我怀疑这就是他们弃用 API.

的原因

看起来有一个 ServiceManagement API/Framework used to load LaunchAgents. Looks SMJobBless 是唯一可用的方法

在 ObjC 中,您始终可以使用 NSTask 来使用 launchctl command-line.

操作守护进程和代理

示例:

- (BOOL)isDaemonLoaded:(NSString *)daemonLabel {
    NSTask *task = [NSTask new];
    [task setLaunchPath:@"/bin/launchctl"];
    [task setArguments:@[@"list", daemonLabel]];
    [task launch];
    [task waitUntilExit];
    // return code 0 is for loaded daemon, and much info is in stdout, and 113 or other nonzero value when daemon is not loaded
    return ([task terminationStatus] == 0);
}

或者,starting/stopping 已启动的全局守护程序:

typedef NS_ENUM(BOOL, DaemonState)
{
    DaemonStateOff = NO,
    DaemonStateOn = YES
};
- (BOOL)switchGlobalDaemon:(NSString *)daemonLabel 
                     state:(DaemonState)newState {
    NSString *command = (newState == OITStateOn) ? @"load" : @"unload";
    NSString *daemonPath = [[@"/Library/LaunchDaemons" stringByAppendingPathComponent: daemonLabel] stringByAppendingPathExtension:@"plist"];
    if (NO == [[NSFileManager defaultManager] fileExistsAtPath:daemonPath])
        return NO;

    NSTask *task = [NSTask new];
    [task setLaunchPath:@"/bin/launchctl"];
    [task setArguments:@[command, daemonPath]];
    [task launch];
    [task waitUntilExit];
    return ([task terminationStatus] == 0);
}

当然任何launchctl命令都可以这样包装。这样做看起来很愚蠢,但由于 Apple 删除了编程 API 并留给我们 launchctl...

无论如何,有很多方法可以“直接”管理守护进程和代理 - 通过在 .plist 中设置规则,然后让 launchd 应用规则。

只是有时会考虑 MacOS 域之外的业务逻辑(比如 - 来自远程服务器的通知,通过 push-notifications,告诉您关闭某些服务,因为客户停止付款...)或更改 运行 服务的配置 - 但在您重新启动它之前不会生效。所以知道这个技巧可能还是有用的。