Files
pangolin/docs/ios-ipad-support-design.html
T
wangjia 84912f4dea docs(client/ios): iOS+iPad 支持设计方案(TestFlight 里程碑)+ 新建 docs 索引
五条工作线:libbox iOS 框架 / Provider 对齐 macOS 修复 / App Group IPC /
iPad 布局横屏适配 / TestFlight 分发;头号风险 NE ~50MB 内存红线设双闸。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:06:20 +08:00

194 lines
13 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iOS + iPad 支持设计方案 — Pangolin</title>
<style>
:root{
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
.wrap{max-width:920px;margin:0 auto;padding:48px 24px 96px}
h1{font-size:30px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
.sub{color:var(--fg2);font-size:15px;margin:0 0 32px}
h2{font-size:21px;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
h3{font-size:16px;margin:28px 0 8px;color:var(--accent2)}
p{margin:10px 0}
code{font-family:var(--mono);font-size:.88em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
pre{background:#0a0c11;border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:13px;line-height:1.55;color:#cdd3df}
pre .c{color:#6b7385}
pre .r{color:var(--bad)}
pre .g{color:var(--ok)}
pre .y{color:var(--warn)}
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
.tag.bad{background:rgba(224,106,106,.16);color:var(--bad)}
.tag.ok{background:rgba(94,194,122,.16);color:var(--ok)}
.tag.warn{background:rgba(224,184,79,.16);color:var(--warn)}
.tag.info{background:rgba(95,176,201,.16);color:var(--accent2)}
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:16px 0}
.card.root{border-left:3px solid var(--accent)}
.card.risk{border-left:3px solid var(--bad)}
.card h3{margin-top:0}
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:14px}
th,td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:top}
th{color:var(--fg2);font-weight:600;font-size:13px}
td code{font-size:.85em}
.ok-c{color:var(--ok)} .bad-c{color:var(--bad)} .warn-c{color:var(--warn)}
ul,ol{padding-left:22px;margin:10px 0}
li{margin:5px 0}
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
.kbd{font-family:var(--mono);font-size:.85em;color:var(--accent)}
.small{color:var(--fg2);font-size:13px}
hr{border:none;border-top:1px solid var(--border);margin:40px 0}
a{color:var(--accent2)}
</style>
</head>
<body>
<div class="wrap">
<h1>iOS + iPad 支持设计方案</h1>
<p class="sub">Pangolin Flutter 客户端 · 目标里程碑 <span class="tag info">TestFlight 公测</span> · 分支 <code>feature/pangolin-ios</code> · 2026-06-22</p>
<div class="lead">
<p style="margin:0"><strong>一句话</strong>:iOS 不是从零起步,而是停在一个 “M3 PoC” 半成品上 —— Xcode 工程、Swift 源码、entitlements、iPad 布局骨架都在,唯一硬阻塞是 <code>Libbox.xcframework</code> 只编了 macOS 切片。本方案把 iOS 隧道从「能编译的桩」推到「真机能连通、扛得住 NE 内存红线、可上 TestFlight」,<strong>iPad 跟着 iPhone 一起好</strong>(同一个 iOS target,非单独版本)。</p>
</div>
<h2>1. 目标与范围</h2>
<table>
<tr><th>维度</th><th>本轮做</th><th>本轮不做</th></tr>
<tr><td>连通性</td><td>iPhone / iPad 真机真正连上网、出网走自有节点</td><td></td></tr>
<tr><td>分发</td><td>App Store Connect 配置 + 描述文件 + TestFlight 外部测试</td><td>完整 App Store 上架审核合规(隐私清单细节、审核问答)</td></tr>
<tr><td>iPad</td><td>布局适配 + 横屏(宽屏主从/侧边栏,6 屏不拉伸)</td><td>Split View / Stage Manager 动态重排、外接键盘/指针/拖拽</td></tr>
<tr><td>平台</td><td>iOS 12+,设备族 iPhone + iPad(已是 <code>"1,2"</code></td><td>tvOS / visionOS</td></tr>
</table>
<p class="small">前置条件已具备:付费 Apple Developer 账号(TeamID <code>BYL4KQHMTN</code>),可上 App Store Connect。</p>
<h2>2. 现状盘点(代码已有什么)</h2>
<table>
<tr><th></th><th>状态</th><th>说明</th></tr>
<tr><td>Xcode 工程接线</td><td><span class="tag ok">已有</span></td><td><code>Runner</code> + <code>PacketTunnel</code> 两 target 已接好;设备族 iPhone+iPad;部署目标 iOS 12</td></tr>
<tr><td>iOS Swift 源码</td><td><span class="tag warn">PoC 桩</span></td><td><code>PacketTunnelProvider.swift</code>(20KB) / <code>VpnManager.swift</code> / <code>MemoryMonitor.swift</code> 在,但 Provider 未真正接通 libbox</td></tr>
<tr><td>entitlements</td><td><span class="tag ok">已有</span></td><td>正确的 iOS 格式:NE <code>packet-tunnel-provider</code> + App Group <code>group.com.pangolin.pangolinVpn</code></td></tr>
<tr><td>Bundle ID</td><td><span class="tag ok">已定</span></td><td>主 App <code>com.pangolin.pangolinVpn</code>,扩展 <code>com.pangolin.pangolinVpn.PacketTunnel</code></td></tr>
<tr><td>Libbox.xcframework</td><td><span class="tag bad">缺 iOS 切片</span></td><td>当前只有 <code>macos-arm64_x86_64</code>,缺 <code>ios-arm64</code> + <code>ios-arm64-simulator</code></td></tr>
<tr><td>DEVELOPMENT_TEAM</td><td><span class="tag bad">未设</span></td><td>iOS 工程未填 teammacOS 已填 <code>BYL4KQHMTN</code></td></tr>
<tr><td>iPad 布局</td><td><span class="tag warn">骨架</span></td><td><code>shell/tablet_shell.dart</code> + <code>core/responsive/form_factor.dart</code> 起步;<code>design/ui_kits/tablet</code> 有原型</td></tr>
</table>
<h3>iOS Provider 与 macOS 的差距(grep 实测)</h3>
<table>
<tr><th>关键修复点</th><th>macOS</th><th>iOS</th></tr>
<tr><td>后台队列启 libbox(防三方死锁)</td><td class="ok-c"></td><td class="warn-c">半(有 DispatchQueue.global,未接启动序列)</td></tr>
<tr><td>非空 <code>LibboxOverrideOptions()</code>(防 SIGSEGV</td><td class="ok-c"></td><td class="bad-c"></td></tr>
<tr><td>阻塞式 <code>startDefaultInterfaceMonitor</code></td><td class="ok-c"></td><td class="bad-c"></td></tr>
<tr><td><code>startOrReloadService</code> 真正拉起 sing-box</td><td class="ok-c"></td><td class="bad-c"></td></tr>
<tr><td>DNS 劫持首条规则</td><td>服务端渲染下发</td><td>服务端渲染下发(同源,无需客户端改)</td></tr>
</table>
<h2>3. 架构决策:iOS Provider 实现策略</h2>
<div class="card root">
<h3>方案 A —— 移植成独立 iOS 版,两端各自维护 <span class="tag ok">已选</span></h3>
<p>仓库本就分 <code>macos/PacketTunnel</code><code>ios/PacketTunnel</code>。两端是<strong>真分叉</strong>System Extension vs App Extension、App Group 前缀 <code>BYL4KQHMTN.</code> vs <code>group.</code>、iOS 独有 ~50MB 内存红线。接受可控重复,在 iOS Provider 文件头放一份「与 macOS 的 parity 对照」注释防漂移。</p>
<p class="small"><strong>取舍</strong>:成本最低、最贴合现有结构。</p>
</div>
<div class="card">
<h3>方案 B(未选)—— 抽公共 Swift core 两端共享</h3>
<p>把 interface monitor / libbox 启动序列 / 配置 IPC 抽成共享文件,平台只留差异 shim。无漂移,但要给 ios + macos 两个独立 Flutter 工程都接好跨 target 共享文件,Xcode 这块很折腾。抽象收益压不过两套工程的接线成本。</p>
</div>
<h2>4. 五条工作线</h2>
<div class="card">
<h3>① libbox iOS 框架(解阻塞)</h3>
<ul>
<li><code>bash scripts/build-libbox.sh apple</code>(不带 platform)→ 产出含 <code>ios-arm64</code>(真机) + <code>ios-arm64-simulator</code> 切片的 <code>Libbox.xcframework</code>,放 <code>client/ios/Frameworks/</code></li>
<li>Xcode 接线照 macOS 铁律:<strong>只 Link 不 Embed</strong>(静态库);<code>PacketTunnel</code><code>OTHER_LDFLAGS=""</code> 切断 CocoaPods 继承;<code>otool -L</code> 验扩展二进制零 <code>@rpath</code> 外部依赖。</li>
<li>iOS 真机签名走嵌入式 provisioning(比 macOS Developer ID 站外分发简单,无需 sysext 公证/staple)。</li>
</ul>
</div>
<div class="card">
<h3>② iOS Provider 对齐 macOS 修复</h3>
<ul>
<li>移植四件套:后台队列启 libbox、<strong>非空 <code>LibboxOverrideOptions()</code></strong><strong>阻塞式 <code>startDefaultInterfaceMonitor</code></strong>(阻塞到首个 path 更新再返回)、<code>startOrReloadService</code> 真正拉起 sing-box。</li>
<li>配置由服务端 <code>BuildClientConfig</code> 渲染原样下发,客户端不拼;DNS 劫持首条规则随配置下发。</li>
<li>iOS 专属:接 <code>MemoryMonitor</code>(已写好)在真机跑满配置打点,验 NE 进程峰值 &lt; 设备 jetsam 上限。</li>
</ul>
</div>
<div class="card">
<h3>③ App Group / IPC</h3>
<ul>
<li>entitlements 已是正确的 iOS <code>group.com.pangolin.pangolinVpn</code>,无需改格式。</li>
<li><code>ios/Runner.xcodeproj</code><code>DEVELOPMENT_TEAM = BYL4KQHMTN</code>(主 App + 扩展两 target)。</li>
<li><code>VpnManager.swift</code>(主 App)↔ Provider 经 App Group UserDefaults 通 status/stats(含崩溃恢复读取)。</li>
</ul>
</div>
<div class="card">
<h3>④ iPad UI 适配(布局 + 横屏)</h3>
<ul>
<li><code>shell/tablet_shell.dart</code> + <code>core/responsive/form_factor.dart</code> 做成完整宽屏主从/侧边栏。</li>
<li>6 个 screenconnect / nodes / stats / settings / account / contact)在 iPad 竖/横屏不拉伸不空旷,对照 <code>design/ui_kits/tablet</code> 原型还原。</li>
<li>Info.plist 开 iPad 全向 orientation;遵守设计 token 单源(颜色走 token,不硬编码)。</li>
</ul>
</div>
<div class="card">
<h3>⑤ 分发到 TestFlight</h3>
<ul>
<li><strong>需用户在开发者后台手点</strong>(我给逐步清单):注册 App ID <code>com.pangolin.pangolinVpn</code> + extension ID + 勾 NE capability(packet-tunnel-provider) + 建 App Group。</li>
<li>出口合规 <code>ITSAppUsesNonExemptEncryption</code>、Archive、上传、TestFlight 外部测试 beta 审核(比正式审核轻)。</li>
</ul>
</div>
<h2>5. 头号风险:iOS NE 内存红线</h2>
<div class="card risk">
<p><strong>iOS Network Extension 进程 ~50MB 内存硬顶(旧设备 ~15MB)× sing-box + 远程 rule-set</strong><br>
配置层 <code>clientconfig.go</code> 的国内分流走<strong>远程拉</strong> <code>geoip-cn.srs</code> / <code>geosite-cn.srs</code>,启动期下载进内存,在 NE 进程里是 jetsam 杀进程的主因。</p>
<p style="margin-bottom:0"><strong>两道闸:</strong></p>
<ol style="margin-top:6px">
<li><strong>先测</strong>:用 <code>MemoryMonitor</code> 在真机跑满配置,拿到峰值 resident 再决定。</li>
<li><strong>不够就瘦身</strong>:落 #5 TODO 的<strong>本地 <code>.srs</code> 预取</strong>(避免启动期下载进内存),必要时 iOS 走精简 rule-set。</li>
</ol>
<p class="small" style="margin-bottom:0">⚠️ <strong>TestFlight 推送以「内存实测通过」为前置闸门</strong> —— 内存不过关不推。</p>
</div>
<h3>其他风险</h3>
<table>
<tr><th>风险</th><th>缓解</th></tr>
<tr><td>iOS Provider 移植引入新死锁/崩溃</td><td>严格照 macos-sysext 排障 runbook 的铁律;真机 device console 验证</td></tr>
<tr><td>受限网络封高位端口</td><td>REALITY 数据口优先走节点 443(与 iPhone/macOS 同策略)</td></tr>
<tr><td>iPad 布局漂移设计稿</td><td>对照 <code>design/ui_kits/tablet</code> 原型;遵守 token 单源</td></tr>
</table>
<h2>6. 验收标准(Definition of Done</h2>
<ol>
<li><code>scripts/build-libbox.sh apple</code> 产出含 iOS 切片的 xcframework<code>otool -L</code> 验扩展零外部 <code>@rpath</code> 依赖。</li>
<li>iPhone 真机:开关 VPN → sing-box 真正起来 → 能打开境外站点(DNS 不失败)。</li>
<li>iPad 真机:同上连通;竖/横屏布局对照原型无拉伸/空旷。</li>
<li><code>MemoryMonitor</code> 真机峰值 &lt; 设备 jetsam 上限(10 分钟稳定运行不被杀)。</li>
<li>成功 Archive 并上传 TestFlight,外部测试者可安装并连通。</li>
</ol>
<h2>7. 不做(YAGNI</h2>
<ul>
<li>Split View / Slide Over / Stage Manager 动态重排(窗口变窄回退手机布局即可,不做专门重排)。</li>
<li>外接键盘快捷键 / 指针 hover / 拖拽等 iPad 原生交互。</li>
<li>完整 App Store 上架审核合规(留待 TestFlight 稳定后另起一轮)。</li>
<li>tvOS / visionOS。</li>
<li>iOS Provider 与 macOS 抽公共 Swift core(接受可控重复)。</li>
</ul>
<hr>
<p class="small">下一步:进 writing-plans 出逐步实施计划。本文档登记于 <a href="index.html">docs/index.html</a></p>
</div>
</body>
</html>