docs(ios): record MLNStyle nil peer tap crash investigation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
152
docs/20_调查/iOS地图点击重叠Source触发MLNStyle空Peer崩溃调查_v1.md
Normal file
152
docs/20_调查/iOS地图点击重叠Source触发MLNStyle空Peer崩溃调查_v1.md
Normal file
@@ -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<ParticleValidationLayerFactory>());
|
||||
addLayerTypeCoreOnly(std::make_unique<RasterParticleLayerFactory>());
|
||||
addLayerTypeCoreOnly(std::make_unique<WeatherColorLayerFactory>());
|
||||
```
|
||||
|
||||
同文件的 `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 验证。**
|
||||
Reference in New Issue
Block a user