merge: iOS 端 PoC M3 + entitlement 申请 [tsk_F5r4Kt9m_wQU]

解决合并冲突(原任务 tsk_nsobbj_rJdy0 分支 maestro/tsk_nsobbj_rJdy0):
两分支修改文件集合无重叠,手动 apply iOS PoC commit (e129f09)。

变更内容:
- client/ios/PacketTunnel/PacketTunnelProvider.swift:完整 M3 实现
  (startTunnel/stopTunnel/handleAppMessage/LibboxPlatformInterface)
- client/ios/PacketTunnel/MemoryMonitor.swift:新增 NE 进程内存打点
- client/ios/PacketTunnel/PacketTunnel.entitlements:NE + App Group 权限
- client/ios/Runner/Runner.entitlements:主 App NE + App Group 权限
- client/ios/Runner/VpnManager.swift:红线词修复 + App Group 缓存 + NEVPNStatus 订阅
- client/ios/Runner/Info.plist:NSVPNUsageDescription 红线词修复
- client/ios/PacketTunnel/Info.plist:CFBundleDisplayName 红线词修复
- client/ios/Runner.xcodeproj/project.pbxproj:
  CODE_SIGN_ENTITLEMENTS 添加到全部 build config;MemoryMonitor 加入 Sources
- app/kernel/build-ios.sh:M3 内存裁剪决策记录
- doc/ne-entitlement-申请指引.md:新增 NE entitlement 申请完整指引

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-13 20:14:32 +08:00
parent 628440b3f9
commit 7a1715bd09
10 changed files with 889 additions and 41 deletions
+140
View File
@@ -0,0 +1,140 @@
// MemoryMonitor.swift NE
//
//
// 1. os_proc_available_memory()iOS 13+ NE
// 2. task_info(MACH_TASK_BASIC_INFO) resident size iOS 12
// 3. availableMemory < warningThreshold
// 4. 10 M3
//
// 使
// let monitor = MemoryMonitor()
// monitor.start() //
// monitor.stop() //
// monitor.peakResidentBytes //
//
// NE jetsam
// A9~15 MB residual limit
// A12~50 MB residual limit
// 线availableMemory < 5 MB
import Darwin
import Foundation
import os.log
// os_proc_available_memory iOS 13+ libSystem 退 mach
// Swift os/proc.h @_silgen_name
@_silgen_name("os_proc_available_memory")
private func _os_proc_available_memory() -> UInt64
// MARK: MemoryMonitor
final class MemoryMonitor {
//
/// 10
let interval: TimeInterval
/// 5 MB
let warningThreshold: UInt64
/// os.log
private let log: OSLog
//
private var timer: DispatchSourceTimer?
private let queue = DispatchQueue(label: "com.pangolin.memory-monitor", qos: .utility)
private(set) var peakResidentBytes: UInt64 = 0
private(set) var minAvailableBytes: UInt64 = UInt64.max
private(set) var sampleCount: Int = 0
init(
interval: TimeInterval = 10,
warningThreshold: UInt64 = 5 * 1024 * 1024,
subsystem: String = "com.pangolin.pangolinVpn.PacketTunnel"
) {
self.interval = interval
self.warningThreshold = warningThreshold
self.log = OSLog(subsystem: subsystem, category: "MemoryMonitor")
}
// MARK:
func start() {
stop()
let t = DispatchSource.makeTimerSource(queue: queue)
t.schedule(deadline: .now(), repeating: interval, leeway: .seconds(1))
t.setEventHandler { [weak self] in self?.tick() }
timer = t
t.resume()
os_log("MemoryMonitor: 开始打点,间隔=%.0fs", log: log, type: .info, interval)
}
func stop() {
timer?.cancel()
timer = nil
os_log(
"MemoryMonitor: 停止打点。总采样 %d 次 | 峰值 RSS=%.1f MB | 最小可用=%.1f MB",
log: log, type: .info,
sampleCount,
Double(peakResidentBytes) / 1_048_576,
minAvailableBytes == UInt64.max ? -1.0 : Double(minAvailableBytes) / 1_048_576
)
}
// MARK:
private func tick() {
let resident = residentBytes()
let available = availableBytes()
sampleCount += 1
if resident > peakResidentBytes { peakResidentBytes = resident }
if available < minAvailableBytes { minAvailableBytes = available }
let level: OSLogType = available < warningThreshold ? .error : .info
os_log(
"MemoryMonitor [#%d] RSS=%.1f MB | 可用=%.1f MB%{public}@",
log: log, type: level,
sampleCount,
Double(resident) / 1_048_576,
Double(available) / 1_048_576,
available < warningThreshold ? " ⚠️ 接近上限,建议裁剪 build tags" : ""
)
}
// MARK:
/// resident sizeRSSiOS 12+
func residentBytes() -> UInt64 {
var info = mach_task_basic_info()
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size / MemoryLayout<integer_t>.size)
let result: kern_return_t = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
}
}
return result == KERN_SUCCESS ? UInt64(info.resident_size) : 0
}
/// NE
/// iOS 13+ os_proc_available_memory()
/// iOS 12退 MAX_RSS - resident
func availableBytes() -> UInt64 {
if #available(iOS 13.0, *) {
return _os_proc_available_memory()
} else {
// iOS 12 退 15 MB - RSS
let conservativeLimit: UInt64 = 15 * 1024 * 1024
let resident = residentBytes()
return resident < conservativeLimit ? conservativeLimit - resident : 0
}
}
/// handleAppMessage/getStats
func summaryString() -> String {
let rss = Double(residentBytes()) / 1_048_576
let avail = Double(availableBytes()) / 1_048_576
return String(format: "RSS=%.1fMB avail=%.1fMB peak=%.1fMB samples=%d",
rss, avail,
Double(peakResidentBytes) / 1_048_576,
sampleCount)
}
}