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

iOS真机viewDidLoad调用pickerDoneClick触发EXC_BAD_ACCESS报错排查

问题描述

我在ViewController里包含15个UIPickerView和15个TextField,在TextField的编辑开始方法中创建对应的PickerView。在viewDidLoad方法中,我先通过以下代码设置PickerView的代理与数据源:

self.firstPicker = UIPickerView(frame:CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 216))
self.firstPicker.delegate = self
self.firstPicker.dataSource = self

// 当TextField开始编辑时执行的方法
func pickUpFirstPicker(_ textField : UITextField) {
    self.kodPicker.backgroundColor = UIColor.white
    textField.inputView = self.kodPicker
    
    // 工具栏设置
    let toolBar = UIToolbar()
    toolBar.barStyle = .default
    toolBar.isTranslucent = true
    toolBar.tintColor = UIColor(red: 92/255, green: 216/255, blue: 255/255, alpha: 1)
    toolBar.sizeToFit()
    
    // 添加工具栏按钮
    let doneButton = UIBarButtonItem(title: "OK", style: .plain, target: self, action: #selector(FiltreVC.pickerDoneClick))
    let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
    let cancelButton = UIBarButtonItem(title: "CANCEL", style: .plain, target: self, action: #selector(FiltreVC.pickerCancelClick))
    
    toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
    toolBar.isUserInteractionEnabled = true
    textField.inputAccessoryView = toolBar
}

@objc func pickerDoneClick() {
    // 400行左右的if-else逻辑
    if (Services.list[sampleIndex].pageName == "XYZ" || Services.list[sampleIndex].pageName == "ABC") {
        if (selected == 1) {
            firstView.isHidden = true
            secondView.isHidden = true
            thirdView.isHidden = false
        } else if (selected == 2) {
            firstView.isHidden = false
            secondView.isHidden = false
            thirdView.isHidden = true
            firstPicker.selectRow(0, inComponent: 0, animated: true)
            pickerDoneClick() // 这里有递归调用
        }
    }
}

设置完PickerView的代理和数据源后,我在viewDidLoad中调用一个包含约400行if-else逻辑的函数,该函数会根据条件调用selectRow选中PickerView的行,随后触发pickerDoneClick函数。当通过PickerView工具栏的Done按钮调用pickerDoneClick时功能正常,但在viewDidLoad中调用时,真机上会触发错误Thread 1: EXC_BAD_ACCESS (code=1, address=0x16fc5da60),而模拟器运行无异常。请问该问题的原因是什么,如何解决?


问题原因与解决方案

核心原因分析

  1. UI控件未完全初始化:在viewDidLoad阶段,ViewController的视图虽然已经加载,但IBOutlet(比如firstView、secondView这些)可能还没完成和Storyboard/XIB的绑定,或者视图还没完成布局。此时直接访问这些控件的属性(比如isHidden),在真机的内存布局下会触发野指针访问,而模拟器的内存管理机制相对宽松,所以没崩溃。
  2. 无限递归调用:看你pickerDoneClick里的代码,当selected == 2时,你直接调用了pickerDoneClick(),这会导致无限递归,栈空间被耗尽。模拟器的栈大小可能比真机更大,所以暂时没触发崩溃,但真机更容易出现栈溢出导致的EXC_BAD_ACCESS。
  3. PickerView未就绪:viewDidLoad时PickerView还没被添加到视图层级,数据源也可能还没完全加载,此时调用selectRow触发的回调可能访问了未初始化的资源。

具体解决步骤

1. 延迟初始化操作,确保UI完全就绪

把调用那个400行if-else函数的代码从viewDidLoad移到viewDidAppear,或者用DispatchQueue.main.asyncAfter延迟执行,确保所有UI控件都完成初始化和布局:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // 调用初始化Picker状态的函数
    setupPickerInitialStates()
}

// 或者用延迟执行(如果不需要等到视图完全显示)
override func viewDidLoad() {
    super.viewDidLoad()
    // 其他初始化代码...
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
        self?.setupPickerInitialStates()
    }
}

2. 修复无限递归问题

把pickerDoneClick里的视图更新逻辑抽成单独的函数,避免直接递归调用pickerDoneClick:

@objc func pickerDoneClick() {
    updateViewVisibility()
}

private func updateViewVisibility() {
    guard sampleIndex < Services.list.count else { return } // 避免数组越界
    let pageName = Services.list[sampleIndex].pageName
    if pageName == "XYZ" || pageName == "ABC" {
        switch selected {
        case 1:
            firstView?.isHidden = true
            secondView?.isHidden = true
            thirdView?.isHidden = false
        case 2:
            firstView?.isHidden = false
            secondView?.isHidden = false
            thirdView?.isHidden = true
            firstPicker.selectRow(0, inComponent: 0, animated: true)
            // 这里如果需要再次更新状态,调用updateViewVisibility而非pickerDoneClick,且要加终止条件
            // 比如只有当selected确实变化时才再次调用,否则直接返回
        default:
            break
        }
    }
}

3. 保护UI控件访问,避免nil指针

在访问所有IBOutlet控件时,使用可选链(?.),防止控件未绑定导致的nil访问:

// 把原来的直接访问改成可选链
firstView?.isHidden = true
secondView?.isHidden = true
thirdView?.isHidden = false

4. 检查PickerView的数据源状态

确保在调用selectRow之前,PickerView的数据源(比如Services.list)已经完全加载完成,避免访问数组越界的元素。可以在调用初始化函数前加判断:

private func setupPickerInitialStates() {
    guard !Services.list.isEmpty else {
        // 数据源未加载完成,稍后重试或者提示用户
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
            self?.setupPickerInitialStates()
        }
        return
    }
    // 执行原来的400行if-else逻辑
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:06:41