You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS沙箱Swift应用父子应用跨进程通信实现方案咨询

解决方案

优先方案:CFMessagePort 正确配置与Swift实现

前置配置(解决签名/权限报错问题)

  • 父子应用均开启App Sandbox能力,父应用勾选「Outgoing Connections (Client)」权限,子应用勾选「Incoming Connections (Server)」权限
  • 父子应用加入同一个App Group,组名格式必须为{你的开发者TeamID}.{自定义组名},TeamID可在苹果开发者账号详情页查看
  • 关闭Xcode的「Automatically manage signing」选项,手动选择在苹果开发者后台生成的、已绑定当前App Group的provision profile,即可解决profile显示None required无法修改的问题

多实例适配逻辑

父应用启动每个子应用实例时,通过启动参数为其分配唯一的端口标识,避免多个子实例端口冲突。

Swift代码实现

子应用(服务端)代码

import CoreFoundation

class ChildPortServer {
    private var localPort: CFMessagePort?
    
    // 启动时传入带App Group前缀的唯一端口名
    func start(portName: String) {
        let callback: CFMessagePortCallBack = { _, _, data, _ in
            guard let cfData = data, let received = Data(referencing: cfData) as? Data else {
                return nil
            }
            // 收到父应用消息后处理,可发通知给UI层更新
            DispatchQueue.main.async {
                NotificationCenter.default.post(name: NSNotification.Name("ParentMessageReceived"), object: received)
            }
            // 可选返回响应数据给父应用
            let response = "子应用已收到消息".data(using: .utf8)!
            return Unmanaged.passRetained(response as CFData)
        }
        
        var context = CFMessagePortContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        var freeInfo: DarwinBoolean = false
        guard let port = CFMessagePortCreateLocal(nil, portName as CFString, callback, &context, &freeInfo) else {
            print("端口创建失败,请检查配置")
            return
        }
        localPort = port
        let runloopSource = CFMessagePortCreateRunLoopSource(nil, port, 0)
        CFRunLoopAddSource(CFRunLoopGetCurrent(), runloopSource, .commonModes)
    }
}

父应用(客户端)代码

func sendMessageToChild(portName: String, content: Data) -> Data? {
    guard let remotePort = CFMessagePortCreateRemote(nil, portName as CFString) else {
        print("子应用端口连接失败")
        return nil
    }
    var returnData: Unmanaged<CFData>?
    let status = CFMessagePortSendRequest(
        remotePort,
        0,
        content as CFData,
        3,
        3,
        CFRunLoopMode.defaultMode.rawValue,
        &returnData
    )
    guard status == kCFMessagePortSuccess, let cfResult = returnData?.takeRetainedValue() else {
        return nil
    }
    return Data(referencing: cfResult)
}

备选方案

NSXPCConnection

你之前对XPC的认知存在偏差:NSXPCConnection完全支持有UI的独立进程通信,没有只能用于无界面后台服务的限制,原生Swift支持更友好,不需要手动处理CoreFoundation类型的内存管理,调试成本更低。

App Group共享目录中转

如果不需要实时通信,也可以用App Group共享目录作为中转:父应用为每个子实例生成独立的消息文件写入共享目录,子应用启动时读取对应文件,也可以通过NSFilePresenter监听文件变化实现消息推送,适合大内容传输场景。

内容的提问来源于stack exchange,提问作者Xiao Xiao

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.18 20:24:04