MacOS Swift应用中WireGuard VPN无法获取Rx/Tx数据统计问题
解决MacOS Swift应用中WireGuard VPN统计数据获取失败问题
核心问题排查方向及修复方案
1. 验证VPN服务ID的正确性
你当前使用的findMeterVPNServiceID()可能返回了错误的服务ID,导致无法关联到WireGuard VPN连接。可以通过遍历所有网络服务来确认正确的服务ID:
func findMeterVPNServiceID() -> String? { guard let services = SCNetworkServiceCopyAll() as? [SCNetworkService] else { return nil } for service in services { if let name = SCNetworkServiceGetName(service) as String?, name.contains("WireGuard") { // 替换为你的VPN实际名称 if let serviceID = SCNetworkServiceGetServiceID(service) as String? { print("匹配到VPN服务ID: \(serviceID)") return serviceID } } } return nil }
2. 修正统计数据的字典路径
SCNetworkConnectionCopyStatistics返回的结构可能和你预期的不同,WireGuard的统计数据可能不在"VPN"键下。先打印完整统计字典确认结构:
func getVPNData() -> (rx: String, tx: String) { var rx: String = "N/A" var tx: String = "N/A" if let vpnConnection = vpnConnection, let statsDict = SCNetworkConnectionCopyStatistics(vpnConnection) as? [String: Any] { // 打印完整统计字典,查看实际键名 print("完整统计数据: \(statsDict)") // 尝试直接读取顶层的BytesIn/BytesOut,或查找utun开头的接口键 if let rxInt = statsDict["BytesIn"] as? Int64, let txInt = statsDict["BytesOut"] as? Int64 { let formatter = ByteCountFormatter() formatter.allowedUnits = [.useBytes, .useKB, .useMB] rx = formatter.string(fromByteCount: rxInt) tx = formatter.string(fromByteCount: txInt) } else { // 遍历字典查找可能的接口统计 for (key, value) in statsDict { if key.starts(with: "utun"), let interfaceStats = value as? [String: Any] { if let rxInt = interfaceStats["BytesIn"] as? Int64, let txInt = interfaceStats["BytesOut"] as? Int64 { let formatter = ByteCountFormatter() formatter.allowedUnits = [.useBytes, .useKB, .useMB] rx = formatter.string(fromByteCount: rxInt) tx = formatter.string(fromByteCount: txInt) break } } } } } return (rx, tx) }
3. 激活SCNetworkConnection的RunLoop调度
创建SCNetworkConnectionRef后,必须将其加入RunLoop才能获取实时统计数据,修改Objective-C代码:
- (SCNetworkConnectionRef _Nullable)initializeVPNConnectionWithServiceID:(NSString *)serviceID { SCNetworkConnectionContext context = {0, NULL, NULL, NULL, NULL}; SCNetworkConnectionRef connection = SCNetworkConnectionCreateWithServiceID(NULL, (__bridge CFStringRef)serviceID, ConnectionStatusChanged, &context); if (connection) { // 将连接调度到当前RunLoop的通用模式 SCNetworkConnectionScheduleWithRunLoop(connection, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); } return connection; }
同时在Swift中释放连接时取消调度:
func releaseVPNConnection() { if let connection = vpnConnection { SCNetworkConnectionUnscheduleFromRunLoop(connection, CFRunLoopGetCurrent(), kCFRunLoopCommonModes) vpnConnection = nil } }
4. 确认应用网络权限
确保你的MacOS应用已获得网络访问权限:
- 在
Info.plist中添加NSNetworkUsageDescription键,填写权限说明 - 前往系统偏好设置 > 安全性与隐私 > 隐私 > 网络,确认你的应用已被允许访问网络
内容的提问来源于stack exchange,提问作者narner
相关产品推荐
相关产品推荐

