You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

生产环境Swift UIKit _NativeSet.startIndex.getter崩溃排查求助

生产环境iOS应用崩溃排查求助:_NativeSet.startIndex.getter崩溃点

Crashlytics堆栈追踪

应用: <appName>
平台: Apple
版本: 2.2.14 (30)
问题ID: 6f9f49c30dcc78da2676dcf048076083
会话: 6f812a773f604e83a4c8d9af26a2a63e_DNE_0_v2
时间: 2024年9月9日 周一 19:25:48 GMT+0800(香港标准时间)

索引模块内存地址函数/方法偏移量
0<appName>0x207910specialized _NativeSet.startIndex.getter+4379932944
1<appName>0x1f85f8closure #1 in DestinationVC.configureDataSource()+223 (DestinationVC.swift:223)
...............
40???0x1b15c7154(Missing)

关联代码

configureDataSource方法

private func configureDataSource() {
    let cellRegistration = UICollectionView.CellRegistration<DestinationViewCell, AnimalResult.ID> { [weak self] (cell, indexPath, animalID) in
        guard let self,
              let thisDestination,
              let i = self.destVCAnimals.firstIndex (where: { animalResult in
                  animalResult.id == animalID }) else { return }
        let animal = self.destVCAnimals[i]
        cell.configureCell(destination: thisDestination, animal: animal)
    }
    let sectionHeaderRegistration = UICollectionView.SupplementaryRegistration<SectionHeaderView>(elementKind: SectionHeaderView.ident) { (sectionHeaderView, kind, indexPath) in }
    let layoutHeaderRegistration = UICollectionView.SupplementaryRegistration<LayoutHeaderView>(elementKind: LayoutHeaderView.ident) { (layoutHeaderView, kind, indexPath) in }
    dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
        collectionView, indexPath, animalID -> DestinationViewCell in
        return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: animalID)
    }
    dataSource.supplementaryViewProvider = { [weak self] (collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView? in
        if kind == "header-view" {
            // 分区头部
            let headerView = collectionView.dequeueConfiguredReusableSupplementary(
                using: sectionHeaderRegistration,
                for: indexPath)
            guard let self else { return headerView }
            let sectionIdentifiers = dataSource.snapshot().sectionIdentifiers
            let sectionIconicID = sectionIdentifiers[indexPath.section]
            guard let animalIDs = iconicTaxons[sectionIconicID] else { return headerView }
            if let thisDestination,
               let viewModel {
                headerView.configure(viewModel, iconicTaxon: (id: sectionIconicID, animals: animalIDs), destination: thisDestination)
                if indexPath.section == 0,
                   viewModel.userIsInThisDestination(),
                   thisDestination.headerBlurbIsSet() {
                    addTouchView(headerView, indexPath)
                }
            }
            let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleSectionHeaderTapGesture(_:)))
            headerView.addGestureRecognizer(tapGesture)
            return headerView
        } else {
            // 布局头部
            let layoutHeaderView = collectionView.dequeueConfiguredReusableSupplementary(
                using: layoutHeaderRegistration,
                for: indexPath)
            guard let self else { return layoutHeaderView }
            if let thisDestination {
                layoutHeaderView.configure(destination: thisDestination)
            }
            collectionView.collectionViewLayout.invalidateLayout()
            return layoutHeaderView
        }
    }
    self.configureSnapshot()
}

setupUI方法

private func setupUI() {
    DispatchQueue.main.async { [weak self] in
        guard let self else { return }
        let titleLabel = UILabel()
        let titleLabelText = thisDestination.destinationName ?? thisDestination.myCommunity?.displayName
        titleLabel.text = titleLabelText
        titleLabel.numberOfLines = 3
        titleLabel.textAlignment = .natural
        self.navigationItem.titleView = titleLabel
        self.title = titleLabel.text
        self.navigationController?.navigationBar.topItem?.largeTitleDisplayMode = .automatic
        self.navigationController?.navigationBar.topItem?.title = titleLabelText
        self.navigationController?.navigationBar.topItem?.backButtonDisplayMode = .minimal
        self.navigationController?.navigationBar.topItem?.style = .browser

        titleLabel.layoutIfNeeded()
        self.navigationItem.titleView?.layoutIfNeeded()
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleNavBarTapGesture(_:)))
        self.navigationController?.navigationBar.addGestureRecognizer(tapGesture)
    }
    if #available(iOS 17.0, *) {
        let styleTrait: UITrait = UITraitUserInterfaceStyle.self
        print("styleTrait: \(styleTrait)")
        self.registerForTraitChanges([styleTrait]) { (self: Self, previousTraitCollection: UITraitCollection) in
            self.configureDataSource()
        }
    }
}

viewDidLoad方法

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = .systemBackground
    self.createEmptyView()
    self.configureCollectionView()
    self.applyConstraints()
    self.setupUI()
    self.configureDataSource()
    self.watch()
}

排查与修复建议

1. 修复集合访问越界问题

在supplementaryViewProvider中访问sectionIdentifiers[indexPath.section]时未做边界检查,当collectionView的section数量与snapshot的section数量不一致时,会触发越界访问,进而导致底层_NativeSet集合崩溃。添加边界校验:

let sectionIdentifiers = dataSource.snapshot().sectionIdentifiers
// 添加边界检查
guard indexPath.section < sectionIdentifiers.count else { return headerView }
let sectionIconicID = sectionIdentifiers[indexPath.section]

2. 解决闭包与手势的强引用/僵尸对象问题

  • 手势识别器的强引用:为头部视图和导航栏添加的UITapGestureRecognizer会强引用VC对象,如果VC被释放后手势仍存在,会触发僵尸对象访问。在VC的deinit方法中移除相关手势:
deinit {
    // 移除头部视图的手势
    collectionView?.visibleSupplementaryViews(ofKind: "header-view").forEach { view in
        view.gestureRecognizers?.forEach { $0.removeTarget(self, action: nil) }
    }
    // 移除导航栏的手势
    navigationController?.navigationBar.gestureRecognizers?.removeAll {
        guard let action = $0.action else { return false }
        return NSStringFromSelector(action) == NSStringFromSelector(#selector(handleNavBarTapGesture(_:)))
    }
}
  • 闭包中潜在的已释放对象访问:在cellRegistration闭包中,destVCAnimals如果依赖于thisDestination,当VC释放后该集合可能已被销毁,建议将destVCAnimals改为可选类型,或在访问前确保其有效性:
guard let self,
      let thisDestination,
      let animals = self.destVCAnimals, // 改为可选后先解包
      let i = animals.firstIndex(where: { $0.id == animalID }) else { return }
let animal = animals[i]

3. 避免重复重建DataSource

在iOS17的 trait 变化回调中直接调用configureDataSource()会重复创建数据源,旧的数据源闭包可能仍被collectionView持有,导致新旧数据源交替时的集合访问异常。改为更新快照而非重建数据源:

self.registerForTraitChanges([styleTrait]) { (self: Self, previousTraitCollection: UITraitCollection) in
    // 仅更新快照刷新UI,不重建数据源
    self.configureSnapshot()
    self.collectionView.reloadData()
}

4. 优化异步闭包中的self引用

setupUI中的DispatchQueue.main.async闭包内,导航栏手势的target直接引用self,建议配合deinit中的手势移除逻辑,避免潜在的循环引用。

内容的提问来源于stack exchange,提问作者Ryan Ashton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 13:09:52