Swift:应用程序因EXC_BAD_INSTRUCTION错误而崩溃?

时间:2018-04-26 18:19:59

标签: ios swift viewcontroller exc-bad-instruction

在测验的这一点上,应用程序应该计算最终得分。目前,在最后一个问题上选择答案后崩溃,标题错误出现在第三行:

var score = 0
for question in QuestionController.questionsList {
score += question.selectedAnswerIndex!

以下该文件的完整代码。它在以前的应用程序迭代中运行良好,但今天就开始了。我知道强制解包选项存在问题,但不清楚为什么它只发生在这种情况下:

    import UIKit

class ViewController: UIViewController {

var window: UIWindow?

override func viewDidLoad() {
  super.viewDidLoad()
  self.title="Quiz"
  self.view.backgroundColor=UIColor.white

  setupViews()
}

@objc func btnGetStartedAction() {
  let v=QuestionController()
  self.navigationController?.pushViewController(v, animated: true)
}

func setupViews() {
  self.view.addSubview(lblTitle)
  lblTitle.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 150).isActive=true
  lblTitle.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive=true
  lblTitle.widthAnchor.constraint(equalToConstant: 250).isActive=true
  lblTitle.heightAnchor.constraint(equalToConstant: 250).isActive=true

  self.view.addSubview(btnGetStarted)
  btnGetStarted.topAnchor.constraint(equalTo: lblTitle.bottomAnchor, constant: 20).isActive=true
  btnGetStarted.heightAnchor.constraint(equalToConstant: 50).isActive=true
  btnGetStarted.widthAnchor.constraint(equalToConstant: 150).isActive=true
  btnGetStarted.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive=true
  btnGetStarted.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0).isActive=true
}

let lblTitle: UILabel = {
  let lbl=UILabel()
  lbl.text="Have you ever wondered which character you are on Friends? Answer the question on this quiz."
  lbl.textColor=UIColor.black
  lbl.textAlignment = .center
  lbl.font = UIFont.systemFont(ofSize: 30)
  lbl.adjustsFontSizeToFitWidth = true
  lbl.numberOfLines=0
  lbl.sizeToFit()
  lbl.translatesAutoresizingMaskIntoConstraints=false
  return lbl
}()

let btnGetStarted: UIButton = {
  let btn=UIButton()
  btn.setTitle("Get Started", for: .normal)
  btn.setTitleColor(UIColor.white, for: .normal)
  btn.backgroundColor=UIColor.blue
  btn.layer.cornerRadius=5
  btn.layer.masksToBounds=true
  btn.translatesAutoresizingMaskIntoConstraints=false
  btn.addTarget(self, action: #selector(btnGetStartedAction), for: .touchUpInside)
  return btn
}()
}

struct Question {
  var questionString: String?
  var answers: [String]?
  var selectedAnswerIndex: Int?

}

class QuestionController: UIViewController, UITableViewDelegate, UITableViewDataSource {

  let cellId = "cellId"
  let headerId = "headerId"
  var tableView: UITableView?

  override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    self.tableView?.frame = CGRect(x: 0, y: 64, width: self.view.frame.width, height: self.view.frame.height - 64)
  }

  static var questionsList: [Question] = [Question(questionString: "What is your favorite type of food?", answers: ["Sandwiches", "Pizza", "Seafood", "Unagi"], selectedAnswerIndex: nil), Question(questionString: "What do you do for a living?", answers: ["Paleontologist", "Actor", "Chef", "Waitress"], selectedAnswerIndex: nil), Question(questionString: "Were you on a break?", answers: ["Yes", "No"], selectedAnswerIndex: nil)]

  override func viewDidLoad() {
    super.viewDidLoad()

    navigationItem.title = "Question"

    navigationController?.navigationBar.tintColor = UIColor.white
    navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)

    tableView = UITableView()
    tableView?.dataSource = self
    tableView?.delegate = self
    tableView?.estimatedRowHeight = 140
    tableView?.sectionHeaderHeight = 100
    self.tableView?.rowHeight = UITableViewAutomaticDimension
    self.view.addSubview(self.tableView!)
    tableView?.register(AnswerCell.self, forCellReuseIdentifier: cellId)
    tableView?.register(QuestionHeader.self, forHeaderFooterViewReuseIdentifier: headerId)
    tableView?.tableFooterView = UIView()
  }

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    if let index = navigationController?.viewControllers.index(of: self) {
      let question = QuestionController.questionsList[index]
      if let count = question.answers?.count {
        return count
      }
    }
    return 0
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath as IndexPath) as! AnswerCell
    if let index = navigationController?.viewControllers.index(of: self) {
      let question = QuestionController.questionsList[index]
      cell.nameLabel.text = question.answers?[indexPath.row]
      cell.nameLabel.numberOfLines = 0
      cell.nameLabel.lineBreakMode = .byWordWrapping
    }

    return cell
  }

  func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerId) as! QuestionHeader

    if let index = navigationController?.viewControllers.index(of: self) {
      let question = QuestionController.questionsList[index]
      header.nameLabel.text = question.questionString
      header.nameLabel.numberOfLines = 0
      header.nameLabel.lineBreakMode = .byWordWrapping
    }

    return header
  }

  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    if let index = navigationController?.viewControllers.index(of: self) {
      QuestionController.questionsList[index].selectedAnswerIndex = indexPath.item

      if index < QuestionController.questionsList.count - 1 {
        let questionController = QuestionController()
        navigationController?.pushViewController(questionController, animated: true)
      } else {
        let controller = ResultsController()
        navigationController?.pushViewController(controller, animated: true)
      }
    }
  }

}

class ResultsController: UIViewController {

let resultsLabel: UILabel = {
let label = UILabel()
label.text = "Congratulations! You'd make a great Ross!"
label.contentMode = .scaleToFill
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 30)
return label
}()

override func viewDidLoad() {
super.viewDidLoad()

navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(done(sender:)))

navigationItem.title = "Results"

view.backgroundColor = UIColor.white

view.addSubview(resultsLabel)
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": resultsLabel]))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": resultsLabel]))

let names = ["Ross", "Joey", "Chandler", "Monica", "Rachel", "Phoebe"]

var score = 0
for question in QuestionController.questionsList {
  score += question.selectedAnswerIndex!
}

let result = names[score % names.count]
resultsLabel.text = "Congratulations! \(result)."
}

@objc func done(sender: UIBarButtonItem) {

navigationController?.popToRootViewController(animated: true)

}

}

2 个答案:

答案 0 :(得分:2)

因为question.selectedAnswerIndexnil而崩溃了。通常,您不应该尽可能使用隐式展开运算符(!),因为如果可选项为nil,它将导致应用程序崩溃。而是将其打包在if let语句中,如果它恰好是nil,则会正常处理错误。

虽然您似乎并不期望question.selectedAnswerIndexnil所以我希望您的代码中的其他位置可能会出现逻辑错误。

答案 1 :(得分:1)

问题是由于您在导航控制器的viewControllers数组中使用视图控制器的位置来确定问题索引。 “get started”视图控制器位于0位置,您的第一个“问题”视图控制器位于位置1。

这意味着您永远不会对用户的食物偏好提出问题'0'。

然后,当您遍历问题并强制解开selectedAnswerIndex时,您会崩溃。

一般情况下,除非:

,否则不应强行打开包装
  1. 您知道该值不是nil
  2. 无论如何你无能为力
  3. 第二点是不要试图太聪明。您可以将当前问题索引的简单int传递给视图控制器。虽然这不像使用viewControllers数组来确定问题索引那样“聪明”,但是如果堆栈中的视图控制器数量发生变化,则更容易看到发生了什么并且不会中断。

    class QuestionController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
        let cellId = "cellId"
        let headerId = "headerId"
        var tableView: UITableView?
        var questionIndex = 0
    
        ....
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
            return QuestionController.questionsList[index].answers?.count ?? 0
        }
    
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath as IndexPath) as! AnswerCell
    
            let question = QuestionController.questionsList[questionIndex]
            cell.nameLabel.text = question.answers?[indexPath.row]
            cell.nameLabel.numberOfLines = 0
            cell.nameLabel.lineBreakMode = .byWordWrapping
    
            return cell
        }
    
        func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
            let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerId) as! QuestionHeader
            let question = QuestionController.questionsList[questionIndex]
            header.nameLabel.text = question.questionString
            header.nameLabel.numberOfLines = 0
            header.nameLabel.lineBreakMode = .byWordWrapping
    
            return header
       }
    
    
       func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
           QuestionController.questionsList[questionIndex].selectedAnswerIndex = indexPath.item
    
           if questionIndex < QuestionController.questionsList.count - 1 {
               let questionController = QuestionController()
               questionController.questionIndex = questionIndex+1
               navigationController?.pushViewController(questionController, animated: true)
           } else {
               let controller = ResultsController()
               navigationController?.pushViewController(controller, animated: true)
           }
        }
    }
    

    这个代码不仅可以工作,而且更容易看到发生了什么,而且代码行数更少,因为您不需要从视图控制器数组中展开可选索引;例如,numberOfRowsInSection从7行变为1。

    此外,在这种情况下,我看不出questionString结构的answersQuestion属性是可选的充分理由;一个问题必须有一个问题和一组答案。我可能也会将questionString更改为question - 将属性名称中的类型设置为多余。

    tableView的{​​{1}}属性是使用隐式展开的可选项的好地方 - 您知道会有tableview,因为您的代码所做的第一件事就是创建它。使用隐式展开的可选(QuestionController而不是UITableView!)将使您不必再继续打开它。