Swift 4 数组越界致命错误求助:洗牌问题数组后程序崩溃
解决Swift 4中数组打乱导致的"Index out of range"崩溃问题
我帮你分析下代码里的问题,以及对应的修复方案:
问题根源
你遇到的崩溃主要有两个核心原因:
randomNumber是类属性,只初始化一次:这个变量在类初始化时就生成了固定的随机值,比如第一次是3,但后续调用newQuestion时,questions数组会被清空(因为你在循环里执行了questions.remove(at: randomNumber)),再用这个固定的索引去访问空数组,必然会越界。- 错误地修改了原始
questions数组:每次调用newQuestion,你都会把questions里的元素全部移到shuffled数组,导致原数组被掏空,下次调用时数组为空,自然无法通过索引访问元素。
修复后的完整代码
我调整了你的代码,保留原始数据的同时实现正确的打乱逻辑,并且修复了索引越界问题:
class ThirdViewController: UIViewController { // 保留原始的问题和答案数组,不要直接修改它们 let originalQuestions = ["A", "B", "C", "D", "E"] let originalAnswers = [["1","2","3"],["2","1","3"],["3","2","1"],["1","3","2"],["2","3","1"]] // 当前使用的问题数组(用于打乱,不影响原始数据) var questions: [String] = [] var answers: [[String]] = [] var rightAnswerPlacement: UInt32 = 0 // Label Question @IBOutlet weak var questionLabel: UILabel! // 建议给控件起有意义的名字 @IBAction func buttonTapped(_ sender: UIButton) { // 同样,方法名更清晰 if sender.tag == Int(rightAnswerPlacement) { print("RIGHT") } else { print("WRONG") } newQuestion() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // 别忘了调用父类方法 self.navigationController?.isNavigationBarHidden = true // 初始化时复制原始数据并打乱 resetAndShuffleData() newQuestion() } // 重置并打乱问题和答案数组(保证两者配对) func resetAndShuffleData() { // 先复制原始数据 questions = originalQuestions answers = originalAnswers // 同时打乱问题和答案数组,保持对应关系 let combined = zip(questions, answers).shuffled() questions = combined.map { $0.0 } answers = combined.map { $0.1 } } func newQuestion() { guard !questions.isEmpty else { // 如果问题都用完了,重置并重新打乱 resetAndShuffleData() } // 每次生成新的随机索引,基于当前questions的数量 let randomNumber = Int(arc4random_uniform(UInt32(questions.count))) questionLabel.text = questions[randomNumber] rightAnswerPlacement = arc4random_uniform(3) + 1 // 更新按钮标题 var x = 1 for i in 1...3 { if let button = view.viewWithTag(i) as? UIButton { if i == Int(rightAnswerPlacement) { button.setTitle(answers[randomNumber][0], for: .normal) } else { button.setTitle(answers[randomNumber][x], for: .normal) x = 2 } } } // 如果需要每次用过后移除当前问题,避免重复 // questions.remove(at: randomNumber) // answers.remove(at: randomNumber) } }
关键修改点说明
- 分离原始数据和工作数据:把
originalQuestions和originalAnswers设为常量,作为数据源,工作时用复制后的数组,避免原始数据被破坏。 - 同时打乱配对数组:用
zip把问题和答案配对后再打乱,保证每个问题对应的答案不会错位。 - 每次生成新的随机索引:在
newQuestion内部生成随机数,而不是用类属性的固定值,确保索引始终在当前数组的有效范围内。 - 添加空数组判断:防止所有问题都被移除后出现空数组访问的情况,自动重置数据。
- 优化命名:给控件和方法起更清晰的名字,提升代码可读性。
另外,如果你使用的是Swift 4.0(还未自带shuffled()方法),可以自己实现一个简单的打乱扩展:
extension Collection { func shuffled() -> [Element] { var array = Array(self) array.shuffle() return array } } extension MutableCollection where Indices.Iterator.Element == Index { mutating func shuffle() { let c = count guard c > 1 else { return } for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) { let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount))) let i = index(firstUnshuffled, offsetBy: d) swapAt(firstUnshuffled, i) } } }
内容的提问来源于stack exchange,提问作者Jamie
相关产品推荐
相关产品推荐

