Files
MapLibreParticleLab/docs/20_调查/iOS地图点击重叠Source触发MLNStyle空Peer崩溃调查_v1.md
tei 24105b1be7
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
docs(ios): record MLNStyle nil peer tap crash investigation
2026-10-09 20:44:36 +09:00

9.8 KiB
Raw Blame History

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)

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 行。

- (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。

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. 当前未完成事项

  • 找到 iPad NavSea 原生崩溃日志,确认主线程异常及调用栈
  • 核对 Particle MLNStyle.mm、MLNStyleLayerManager.mm 与 RN MLRNMapView.m 源码
  • 说明直接点击与十字光标查询的不同调用路径
  • 获取本次返回 nil 的具体 Style Layer id/type
  • 编写并执行 Native 修复及目标回归
  • 重新构建 iOS、在实际 iPad 复现原场景并确认不再退出
  • 按 GSRL 验证新版本消费与正式发行(如进入发行)

结论:高可信度机制为 Native Style CoreOnly/缺失 Peer 与 RN 多 Source hit-test 排序不兼容;异常位置已由 .ips 确认,具体 nil Layer 身份与最终修复仍待 Particle iOS 验证。