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),而模拟器运行无异常。请问该问题的原因是什么,如何解决?
核心原因分析
- UI控件未完全初始化:在
viewDidLoad阶段,ViewController的视图虽然已经加载,但IBOutlet(比如firstView、secondView这些)可能还没完成和Storyboard/XIB的绑定,或者视图还没完成布局。此时直接访问这些控件的属性(比如isHidden),在真机的内存布局下会触发野指针访问,而模拟器的内存管理机制相对宽松,所以没崩溃。 - 无限递归调用:看你
pickerDoneClick里的代码,当selected == 2时,你直接调用了pickerDoneClick(),这会导致无限递归,栈空间被耗尽。模拟器的栈大小可能比真机更大,所以暂时没触发崩溃,但真机更容易出现栈溢出导致的EXC_BAD_ACCESS。 - 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

