From 8367984e2304ea3e3a7fbd024286a0c7682e14ab Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Fri, 19 Jun 2026 14:30:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(client/macos):=20PacketTunnelProvider=20?= =?UTF-8?q?=E6=94=B9=E7=94=A8=20completion-handler=20=E7=BB=95=E5=BC=80=20?= =?UTF-8?q?Swift=206.2=20=E7=BC=96=E8=AF=91=E5=99=A8=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swift 6.2.3 在为 async 重写的 startTunnel 生成 ObjC 桥接 thunk 时崩溃 (emitInjectLoadableEnum)。改用 completion-handler 形式(直接是 @objc 原型) 绕开。startTunnel/stopTunnel/handleAppMessage 三个 override 同步改。 Co-Authored-By: Claude Opus 4.8 --- .../PacketTunnel/PacketTunnelProvider.swift | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/client/macos/PacketTunnel/PacketTunnelProvider.swift b/client/macos/PacketTunnel/PacketTunnelProvider.swift index d7ce2a3..800444b 100644 --- a/client/macos/PacketTunnel/PacketTunnelProvider.swift +++ b/client/macos/PacketTunnel/PacketTunnelProvider.swift @@ -30,7 +30,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // App Group 共享容器:放 sing-box 工作目录(缓存/日志)。主 app 与扩展同路径。 private static let appGroup = "group.com.pangolin.pangolin" - override func startTunnel(options: [String: NSObject]?) async throws { + // 用 completion-handler 形式(而非 async):Swift 6.2 编译器在为 async 重写 + // 生成 ObjC 桥接 thunk 时会崩(emitInjectLoadableEnum),completion-handler 直接 + // 就是 @objc 原型,绕开该 bug。成功调 completionHandler(nil),失败传 error。 + override func startTunnel(options: [String: NSObject]?, + completionHandler: @escaping (Error?) -> Void) { log.info("startTunnel") // 1) sing-box JSON:优先取 startVPNTunnel 下发的 options["configContent"], @@ -42,15 +46,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider { configContent = (try? Self.readSharedConfig()) ?? "" } guard !configContent.isEmpty else { - throw NSError(domain: "pangolin.tunnel", code: 1, - userInfo: [NSLocalizedDescriptionKey: "empty sing-box config"]) + completionHandler(NSError(domain: "pangolin.tunnel", code: 1, + userInfo: [NSLocalizedDescriptionKey: "empty sing-box config"])) + return } - // 2) libbox 基础路径(全部落在 App Group 容器内,sysess 沙盒可写)。 + // 2) libbox 基础路径(全部落在 App Group 容器内,扩展沙盒可写)。 guard let base = FileManager.default .containerURL(forSecurityApplicationGroupIdentifier: Self.appGroup) else { - throw NSError(domain: "pangolin.tunnel", code: 2, - userInfo: [NSLocalizedDescriptionKey: "no app group container"]) + completionHandler(NSError(domain: "pangolin.tunnel", code: 2, + userInfo: [NSLocalizedDescriptionKey: "no app group container"])) + return } let work = base.appendingPathComponent("work", isDirectory: true) try? FileManager.default.createDirectory(at: work, withIntermediateDirectories: true) @@ -62,36 +68,41 @@ class PacketTunnelProvider: NEPacketTunnelProvider { setup.workingPath = work.path setup.tempPath = NSTemporaryDirectory() LibboxSetup(setup, &setupError) - if let setupError { throw setupError } + if let setupError { completionHandler(setupError); return } let platform = PangolinPlatformInterface(provider: self) self.platformInterface = platform var newError: NSError? guard let service = LibboxNewService(configContent, platform, &newError) else { - throw newError ?? NSError(domain: "pangolin.tunnel", code: 3) + completionHandler(newError ?? NSError(domain: "pangolin.tunnel", code: 3)); return } self.boxService = service - try service.start() + do { try service.start() } catch { completionHandler(error); return } + completionHandler(nil) // 隧道已起 + return ──────────────────────────────────────────────────────────────────── */ - // 骨架占位:xcframework 接入前,startTunnel 直接抛错,避免静默假成功。 - throw NSError(domain: "pangolin.tunnel", code: 99, userInfo: [ + // 骨架占位:xcframework 接入前,直接回错,避免静默假成功。 + completionHandler(NSError(domain: "pangolin.tunnel", code: 99, userInfo: [ NSLocalizedDescriptionKey: "Libbox 尚未接入(见 docs/p1-macos-system-extension.md 步骤 3)", - ]) + ])) } - override func stopTunnel(with reason: NEProviderStopReason) async { + override func stopTunnel(with reason: NEProviderStopReason, + completionHandler: @escaping () -> Void) { log.info("stopTunnel reason=\(reason.rawValue)") /* if let service = boxService as? LibboxBoxService { try? service.close() } */ boxService = nil platformInterface = nil + completionHandler() } // 主 app 经 sendProviderMessage 下发的控制(切节点等);也可改走 libbox CommandServer。 - override func handleAppMessage(_ messageData: Data) async -> Data? { - nil + override func handleAppMessage(_ messageData: Data, + completionHandler: ((Data?) -> Void)?) { + completionHandler?(nil) } private static func readSharedConfig() throws -> String {