fix(client/macos): PacketTunnelProvider 改用 completion-handler 绕开 Swift 6.2 编译器崩溃
Swift 6.2.3 在为 async 重写的 startTunnel 生成 ObjC 桥接 thunk 时崩溃 (emitInjectLoadableEnum)。改用 completion-handler 形式(直接是 @objc 原型) 绕开。startTunnel/stopTunnel/handleAppMessage 三个 override 同步改。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
// App Group 共享容器:放 sing-box 工作目录(缓存/日志)。主 app 与扩展同路径。
|
// App Group 共享容器:放 sing-box 工作目录(缓存/日志)。主 app 与扩展同路径。
|
||||||
private static let appGroup = "group.com.pangolin.pangolin"
|
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")
|
log.info("startTunnel")
|
||||||
|
|
||||||
// 1) sing-box JSON:优先取 startVPNTunnel 下发的 options["configContent"],
|
// 1) sing-box JSON:优先取 startVPNTunnel 下发的 options["configContent"],
|
||||||
@@ -42,15 +46,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
configContent = (try? Self.readSharedConfig()) ?? ""
|
configContent = (try? Self.readSharedConfig()) ?? ""
|
||||||
}
|
}
|
||||||
guard !configContent.isEmpty else {
|
guard !configContent.isEmpty else {
|
||||||
throw NSError(domain: "pangolin.tunnel", code: 1,
|
completionHandler(NSError(domain: "pangolin.tunnel", code: 1,
|
||||||
userInfo: [NSLocalizedDescriptionKey: "empty sing-box config"])
|
userInfo: [NSLocalizedDescriptionKey: "empty sing-box config"]))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) libbox 基础路径(全部落在 App Group 容器内,sysess 沙盒可写)。
|
// 2) libbox 基础路径(全部落在 App Group 容器内,扩展沙盒可写)。
|
||||||
guard let base = FileManager.default
|
guard let base = FileManager.default
|
||||||
.containerURL(forSecurityApplicationGroupIdentifier: Self.appGroup) else {
|
.containerURL(forSecurityApplicationGroupIdentifier: Self.appGroup) else {
|
||||||
throw NSError(domain: "pangolin.tunnel", code: 2,
|
completionHandler(NSError(domain: "pangolin.tunnel", code: 2,
|
||||||
userInfo: [NSLocalizedDescriptionKey: "no app group container"])
|
userInfo: [NSLocalizedDescriptionKey: "no app group container"]))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
let work = base.appendingPathComponent("work", isDirectory: true)
|
let work = base.appendingPathComponent("work", isDirectory: true)
|
||||||
try? FileManager.default.createDirectory(at: work, withIntermediateDirectories: true)
|
try? FileManager.default.createDirectory(at: work, withIntermediateDirectories: true)
|
||||||
@@ -62,36 +68,41 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
setup.workingPath = work.path
|
setup.workingPath = work.path
|
||||||
setup.tempPath = NSTemporaryDirectory()
|
setup.tempPath = NSTemporaryDirectory()
|
||||||
LibboxSetup(setup, &setupError)
|
LibboxSetup(setup, &setupError)
|
||||||
if let setupError { throw setupError }
|
if let setupError { completionHandler(setupError); return }
|
||||||
|
|
||||||
let platform = PangolinPlatformInterface(provider: self)
|
let platform = PangolinPlatformInterface(provider: self)
|
||||||
self.platformInterface = platform
|
self.platformInterface = platform
|
||||||
|
|
||||||
var newError: NSError?
|
var newError: NSError?
|
||||||
guard let service = LibboxNewService(configContent, platform, &newError) else {
|
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
|
self.boxService = service
|
||||||
try service.start()
|
do { try service.start() } catch { completionHandler(error); return }
|
||||||
|
completionHandler(nil) // 隧道已起
|
||||||
|
return
|
||||||
──────────────────────────────────────────────────────────────────── */
|
──────────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
// 骨架占位:xcframework 接入前,startTunnel 直接抛错,避免静默假成功。
|
// 骨架占位:xcframework 接入前,直接回错,避免静默假成功。
|
||||||
throw NSError(domain: "pangolin.tunnel", code: 99, userInfo: [
|
completionHandler(NSError(domain: "pangolin.tunnel", code: 99, userInfo: [
|
||||||
NSLocalizedDescriptionKey:
|
NSLocalizedDescriptionKey:
|
||||||
"Libbox 尚未接入(见 docs/p1-macos-system-extension.md 步骤 3)",
|
"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)")
|
log.info("stopTunnel reason=\(reason.rawValue)")
|
||||||
/* if let service = boxService as? LibboxBoxService { try? service.close() } */
|
/* if let service = boxService as? LibboxBoxService { try? service.close() } */
|
||||||
boxService = nil
|
boxService = nil
|
||||||
platformInterface = nil
|
platformInterface = nil
|
||||||
|
completionHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主 app 经 sendProviderMessage 下发的控制(切节点等);也可改走 libbox CommandServer。
|
// 主 app 经 sendProviderMessage 下发的控制(切节点等);也可改走 libbox CommandServer。
|
||||||
override func handleAppMessage(_ messageData: Data) async -> Data? {
|
override func handleAppMessage(_ messageData: Data,
|
||||||
nil
|
completionHandler: ((Data?) -> Void)?) {
|
||||||
|
completionHandler?(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func readSharedConfig() throws -> String {
|
private static func readSharedConfig() throws -> String {
|
||||||
|
|||||||
Reference in New Issue
Block a user