From 24105b1be7a2c8bd3a547117a30887fbaa0a308b Mon Sep 17 00:00:00 2001 From: tei Date: Fri, 9 Oct 2026 20:44:36 +0900 Subject: [PATCH] docs(ios): record MLNStyle nil peer tap crash investigation --- ...点击重叠Source触发MLNStyle空Peer崩溃调查_v1.md | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 docs/20_调查/iOS地图点击重叠Source触发MLNStyle空Peer崩溃调查_v1.md diff --git a/docs/20_调查/iOS地图点击重叠Source触发MLNStyle空Peer崩溃调查_v1.md b/docs/20_调查/iOS地图点击重叠Source触发MLNStyle空Peer崩溃调查_v1.md new file mode 100644 index 000000000..8649c71c4 --- /dev/null +++ b/docs/20_调查/iOS地图点击重叠Source触发MLNStyle空Peer崩溃调查_v1.md @@ -0,0 +1,152 @@ +# iOS 地图点击重叠 Source 触发 MLNStyle 空 Peer 崩溃调查 v1 + +- 日期:2026-10-09(Asia/Tokyo) +- 状态:**问题已复现 / 原生崩溃堆栈已确认 / 修复与回归未执行** +- 分类:Particle iOS Native / MapLibre Darwin Style API 与 maplibre-react-native 点击兼容 +- 严重程度:高;普通用户点击海图物体可使 NavSea 直接退出 +- 本文件性质:跨项目缺陷移交与定位证据,**不是已实施修复、测试 PASS 或新版本发行结论**。 + +## 1. 用户复现现象 + +在 iOS NavSea 正常启动并显示地图后: + +1. 直接点击地图上的「唐津ヨット ハーバ」位置点,App 异常退出。 +2. 点击其他普通地图物体,未观察到同样问题。 +3. 打开「+键盘」(十字光标)后,将光标移动到该港湾位置,查询/选取正常,不退出。 + +因此该问题优先定位 **地图直接 Tap 的原生可点击 Source 命中/图层排序链路**,而不是仅根据地点名称推断港湾数据损坏、JS 详情渲染异常或粒子 GPU 绘制故障。 + +## 2. 实际环境与 Git 事实 + +| 项目 | 本次调查事实 | +| --- | --- | +| iOS 设备 | Pleiades,iPad mini(第 5 代,iPad11,2) | +| iOS 崩溃日志声明的系统 | iPhone OS 26.5.2 (23F84) | +| App | NavSea,bundleID `com.navsea.app` | +| App 调查工作树 | `/Users/tei/dev/NavSea-release-v22` | +| App 调查 HEAD | `4bfe5257`(`release/particle-v22-android-debug-20261009`) | +| Particle 本次核对仓库 | `/Users/tei/dev/MapLibreParticleLab`,`particle/main` | +| Particle 调查起点 HEAD | `6df21dac38f072c5ad35b8dfc7ec5217509602bc` | +| 崩溃时间 | 2026-10-09 20:22:40 +0900 | +| 崩溃日志 | 设备端 `NavSea-2026-10-09-202240.ips`(调查时只读复制到 Mac `/tmp/`) | + +设备还存在多份同日晚间 NavSea 崩溃报告,本次详细分析的证据为上述 **20:22:40** 报告。请不要把其余报告自动认定为相同堆栈,除非逐份核查。 + +Particle 工作区在调查前已有其他未跟踪文档,均不属于本问题,不得顺手删除、覆盖或纳入本次提交。 + +## 3. 崩溃证据(直接来自 iPad .ips) + +```text +exception: EXC_CRASH / SIGABRT +exceptionReason.name: NSInvalidArgumentException +exceptionReason.composed_message: + *** -[__NSArrayM insertObject:atIndex:]: object cannot be nil +faultingThread: 0 (com.apple.main-thread) + +-[MLRNMapView didTapMap:] + -> -[MLRNMapView handleTapAtPoint:] + -> -[MLRNMapView getTouchableSourceWithHighestZIndex:] + -> -[MLNStyle layers] + -> -[__NSArrayM insertObject:atIndex:] + -> NSInvalidArgumentException / abort() +``` + +上述调用栈直接证明 **崩溃发生在地图原生 Tap 处理访问 Style Layer 列表时,且 Objective-C 数组插入了 `nil`**。它并不证明具体是哪一个 Style Layer 的 Peer 为 `nil`;这一点需要后续补充定位日志或调试证据。 + +## 4. 已核实的对应代码 + +### 4.1 Darwin Style 列表 getter:空对象缺少防护 + +文件:`platform/darwin/src/MLNStyle.mm`,调查时约第 285–293 行。 + +```objc +- (NSArray<__kindof MLNStyleLayer *> *)layers { + auto layers = self.rawStyle->getLayers(); + NSMutableArray<__kindof MLNStyleLayer *> *styleLayers = + [NSMutableArray arrayWithCapacity:layers.size()]; + for (auto layer : layers) { + MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer]; + [styleLayers addObject:styleLayer]; + } + return styleLayers; +} +``` + +`layerFromMBGLLayer:` 位于同一文件约第 376 行。该方法在已有 Peer 缺失时调用 `LayerManagerDarwin::createPeer(rawLayer)`;若工厂无法产生 Objective-C Peer,返回值可能为 `nil`,随后无条件 `addObject` 会直接触发本次异常。 + +### 4.2 部分 Particle/Weather Layer 仅注册为 CoreOnly + +文件:`platform/darwin/src/MLNStyleLayerManager.mm`。 + +```cpp +addLayerTypeCoreOnly(std::make_unique()); +addLayerTypeCoreOnly(std::make_unique()); +addLayerTypeCoreOnly(std::make_unique()); +``` + +同文件的 `LayerManagerDarwin::createPeer`:当 `getPeerFactory(layer->getTypeInfo())` 不存在时返回 `nullptr`。因此以上 CoreOnly Layer **是造成空 Peer 的高优先级怀疑对象**,但需要定位日志确认实际 layer id/type,不能将具体类型预写为已证实。 + +### 4.3 RN MapView:多 Source 命中触发列表遍历 + +NavSea 当前安装依赖源码: +`node_modules/@maplibre/maplibre-react-native/ios/components/map-view/MLRNMapView.m`。 + +- `handleTapAtPoint:` 查询所有可点击 Source 的命中;存在命中时调用 `getTouchableSourceWithHighestZIndex:`。 +- `getTouchableSourceWithHighestZIndex:` 在 `touchableSources.count == 1` 时直接返回唯一 Source。 +- 在 **同时命中两个或更多可点击 Source** 时,方法读取 `self.style.layers` 并逆序遍历,决定顶部 Source;因此会进入第 4.1 节的危险路径。 + +崩溃堆栈已进入 `getTouchableSourceWithHighestZIndex:` 的 `self.style.layers`,符合多 Source 命中逻辑。需要现场记录所命中的具体 Source / layer IDs,以确认唐津位置的重叠对象组成。 + +### 4.4 NavSea 点击与十字光标是不同路径 + +- 直接点击地图:`src/map/MapScreen.tsx` 的 `MapLibreMap onPress`,以及 `src/map/layers/MooringSource.tsx` 的 `GeoJSONSource onPress`。 +- 港湾点直点 Host 回调:`src/map/cursor/crossCursorEngine.ts` 的 `handleMooringPress`。 +- 「+键盘」选取:`crossCursorEngine.ts` 中的 `queryRenderedFeaturesAtPointCompat` / `queryRenderedFeaturesInRectCompat` 查询。 + +本次异常在原生触摸处理阶段已发生;**即使 JS 侧在 `onPress` 增加 try/catch,也无法覆盖此 Objective-C SIGABRT**。 + +## 5. 修复方向(待 Particle iOS 实施者决定) + +1. **首先精确定位**:在不改变业务和地图 Style 行为的前提下,记录 `layerFromMBGLLayer:` 转换失败的 raw layer id/type、Peer Factory 类型及命中 Source / Layer 列表,确认 CoreOnly Layer 是否为实际来源。 +2. **修复 Darwin 公共访问契约**:`MLNStyle.layers` 不得把 `nil` 加入 `NSMutableArray`;优先设计与现有 CoreOnly Layer 共存的稳定方案。可评估安全过滤不可桥接 Layer,或补齐对应的 Peer/排序能力,但不可仅凭猜测引入无意义的 ObjC wrapper。 +3. **保持集合访问一致性**:若采取过滤不可桥接 Layer 的方式,须同时检查 `countOfLayers`、`objectInLayersAtIndex:`、`getLayers:range:`、KVC/KVO 以及插入/移除行为,避免 getter 屏蔽后出现数量/索引语义不一致或再次写入 `nil`。 +4. **保持点击 z-order 正确性**:RN 的多 Source 命中不能因为跳过 CoreOnly Layer 而改变真实可点击 Layer 的前后顺序;必要时直接在 Native 查询层顺序而不是依赖不完整 ObjC Peer 列表。 +5. **限定修改范围**:优先在 Particle iOS Native / MapLibre 层完成,不通过 NavSea JS 屏蔽唐津地点、删除天气层或建立私有桥接来回避崩溃。若确实需要改 RN 公共接口或跨项目契约,应返回 Integration 项目协调。 + +**切勿直接删除 Particle、WeatherColor 或 Validation 的 Core Layer 来“消除异常”**;它们是正式渲染能力。 + +## 6. 修复后必须验证的场景 + +| 场景 | 期望 | +| --- | --- | +| 同位置只命中一个可点击 Source | 正常选取,无崩溃 | +| 两个及以上可点击 Source 重叠(重点复现) | 正确选中 z-order 最高且有效的 Source,无崩溃 | +| 唐津ヨット ハーバ 直接点击 | 不退出,港湾选取/摘要功能正常 | +| 「+键盘」移动到同一港湾 | 行为与修复前保持一致 | +| 其他海图对象直接点击 | 继续正常,无点击回归 | +| Style 含 RasterParticleLayer / WeatherColorLayer / ParticleValidationLayer 组合 | Style introspection 稳定,不向数组插入 nil | +| 天气/粒子图层启用与关闭后重复点击 | 无崩溃、无源层级混乱、无隐藏的点击遮挡 | +| `layers` getter、KVC 索引 getter、层增删 | 不出现 nil/越界,计数与索引契约保持一致 | + +最少应使用 **Mac 构建出的实际 iOS Native 二进制 + iPad 真机** 做点击复现/回归。仅能编译或仅有单元测试通过,不等于用户场景已修复。 + +## 7. 责任、验收与完成条件 + +- **Particle iOS**:调查具体 nil layer、修复 Darwin Style API/多 Source 点击兼容、实施 iOS 构建与真机验证。 +- **Particle Shared Core**:仅在确认是通用 Layer/Peer 协议缺陷时参与;不得未经证据改变 Android 行为。 +- **NavSea**:在收到经过验证的新 iOS 发行后进行最小消费端验收;业务层不承担原生崩溃掩盖责任。 +- **Integration 项目**:若涉及公共 API、跨平台协议、消费版本协调,则在现有 COORD/RFC/ADR/Contract/Status 框架内正式记录。 + +修复完成报告应写明:引发空 Peer 的真实 layer id/type、实际改动文件、验证命令及结果、真机版本和复现结果、Git commit/分支,以及正式发行时遵照 GSRL 的 Source Lock/获取方式/校验材料。**本调查报告本身不构成新发行,也不授权覆盖现有版本。** + +## 8. 当前未完成事项 + +- [x] 找到 iPad NavSea 原生崩溃日志,确认主线程异常及调用栈 +- [x] 核对 Particle `MLNStyle.mm`、`MLNStyleLayerManager.mm` 与 RN `MLRNMapView.m` 源码 +- [x] 说明直接点击与十字光标查询的不同调用路径 +- [ ] 获取本次返回 `nil` 的具体 Style Layer id/type +- [ ] 编写并执行 Native 修复及目标回归 +- [ ] 重新构建 iOS、在实际 iPad 复现原场景并确认不再退出 +- [ ] 按 GSRL 验证新版本消费与正式发行(如进入发行) + +**结论:高可信度机制为 Native Style CoreOnly/缺失 Peer 与 RN 多 Source hit-test 排序不兼容;异常位置已由 .ips 确认,具体 nil Layer 身份与最终修复仍待 Particle iOS 验证。**