Swift 2 到 3 迁移错误(libc++abi.dylib:以 NSException 类型的未捕获异常终止)

Swift 2 to 3 Migration Error (libc++abi.dylib: terminating with uncaught exception of type NSException)

我的一个控制器在 Swift 2.

中不存在时奇怪地导致运行时错误崩溃

我得到的错误是。

libc++abi.dylib:以 NSException

类型的未捕获异常终止

我附上了一张屏幕截图,显示我已连接所有界面构建器元素。

我做了一些实验,看看错误发生在什么时候,我认为它可能与设置 textView 有关,但不确定。

1.) 注意控制台消息在 print("Draw Undo HTML") 后停止。我最初认为错误与之后的行有关,因为我从未看到控制台消息 print("Set attributed text accordingly").

2.) 如果我注释掉行 incrementListTextView.attributedText = html.html2AttributedString

错误仍然会发生,只是代码的其余部分在它发生之前运行。很奇怪,但是看到错误似乎首先出现在该行附近,我认为它与 TextView 有关但不确定它可能是什么。

我也附上了这个场景的图片。

InventoryItemController.Swift(完整文件参考)

//
//  InventoryItemController.swift
//  Inventory Counter
//
//  Created by Joseph Astrahan on 4/3/16.
//  Copyright © 2016 Joseph Astrahan. All rights reserved.
//

import UIKit
import CoreData

class InventoryItemController: UIViewController, UITextFieldDelegate {

    var inventoryItem : Inventory?
    var m_incrementAmount = 0 //amount it will increment everytime you press (+)
    var m_itemHistory = [Int]() //create array to store undo/redo history
    var m_undoIndex = 0


    @IBOutlet weak var inventoryBarCodeLabel: UILabel!

    @IBOutlet weak var inventoryTotalLabel: UILabel!

    //List of increments
    @IBOutlet weak var incrementListTextView: UITextView!

    //Amount to increment by
    @IBOutlet weak var incrementAmountTextField: UITextField!

    @IBOutlet weak var inventoryNameNavItem: UINavigationItem!

    @IBAction func resetBarButtonAction(_: UIBarButtonItem) {
        //Present 'Are you sure?' Dialog & Reset Vars.

        // create the alert in local scope (no need for weak or unowned reference to self in closures)
        let alert = UIAlertController(title: "Are You Sure?", message: "This will delete all the counts you have done.", preferredStyle: UIAlertControllerStyle.alert)

        // add an action (button)
        alert.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.default, handler: { action in
            self.resetTotals()
        }))

        alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil))

        // show the alert
        self.present(alert, animated: true, completion: nil)

    }

    func resetTotals(){
        print("resetting...")
        m_itemHistory = [Int]()
        m_itemHistory.append(0)//first count is always 0 after reset.

        m_undoIndex = 0 //set back to 0

        updateTotal()//update total and save to disk.

        print("reset!...")
    }

    @IBAction func addInventoryButtonAction(_: UIButton) {

        //When you add you have to first trim the array of everything that was after it.  The redo history is now gone.
        let slice = m_itemHistory[0...m_undoIndex]
        m_itemHistory = Array(slice)//create new int array from the slice

        m_incrementAmount = Int(incrementAmountTextField.text!)!
        m_itemHistory.append(m_incrementAmount)

        m_undoIndex = m_undoIndex + 1 //add to the index, because undo always happens from last added~

        //Update addCount on actual inventoryItem (save to database)
        updateTotal()
    }

    @IBAction func undoButtonAction(_: UIButton) {
        print("undo")

        m_undoIndex = m_undoIndex - 1

        updateTotal()
    }

    @IBAction func redoButtonAction(_: UIButton) {
        print("redo")

        m_undoIndex = m_undoIndex + 1

        updateTotal()
    }


    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

    func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

    func textFieldDidEndEditing(_ textField: UITextField){
       textField.resignFirstResponder()
    }

    func dismissKeyboard() {
        //Causes the view (or one of its embedded text fields) to resign the first responder status.
        view.endEditing(true)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(LoginViewController.dismissKeyboard))
        self.view.addGestureRecognizer(tap)

        print("Showing Inventory Item In Question")
        print(inventoryItem)
        print("Inventory Name="+(inventoryItem?.name!)!)

        //inventoryNameLabel.text = inventoryItem.name!
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        //add observer for contentSize so it centers vertically
        incrementListTextView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.new, context: nil)

        //Draw inventory name & barcode
        inventoryNameNavItem.title = inventoryItem?.name
        inventoryBarCodeLabel.text = inventoryItem?.barcode

        //Load first time only when view appears to set default
        m_itemHistory.append(Int((inventoryItem?.addCount!)!))//add initial value of last 'count'

        m_undoIndex = 0 //reset to start.

        updateTotal() //updates total to screen

        print("Finished UpdateTotal, Should be Showing Screen Now")

    }

    func updateTotal(){

        //Get the max Index we can count to -1 (because arrays are 0 indexed)
        let historyTotalMaxIndex = m_itemHistory.count - 1
        print("historyTotalCheck: HistoryTotalMaxIndex=\(historyTotalMaxIndex)")

        //Current state of undoIndex
        print("Init: m_undoIndex =\(m_undoIndex)")

        //Do checks to prevent out of index bounds.
        if(m_undoIndex<0){
            m_undoIndex = 0
        }

        if(m_undoIndex>historyTotalMaxIndex){
            m_undoIndex = historyTotalMaxIndex
        }

        //After modifying...
        print("Current: m_undoIndex =\(m_undoIndex)")


        //Draw HTML
        var html = "<html><font size=\"5\"><font color=\"#008800\"><center>"
        for index in 0...m_undoIndex {
            let increment = m_itemHistory[index]
            html = html + "+\(increment), "
        }
        html = html + "</center></font></font></html>"

        print(html)

        print("Draw Undo HTML")

        incrementListTextView.attributedText = html.html2AttributedString

        print("Set attributed text accordingly")

        //get sum of itemHistory

        let slice = m_itemHistory[0...m_undoIndex] //returns slice of the array we want.
        let sumArray = Array(slice)//create new int array from the slice

        print("SumArray Created")

        let sum = sumArray.reduce(0, +) //now we can sum up that new sliced array
        inventoryItem?.addCount = sum as NSNumber? //set the add count

        print("Reduced the Array")

        //reset html to different now
        html = "<font size=\"10\"><center>Current: \(inventoryItem?.currentCount!) , <font color=\"#008800\"> Counted: \(inventoryItem?.addCount!)</font></center></font>"

        inventoryTotalLabel.attributedText = html.html2AttributedString

        print("save the context")
        //Save the changes
        (UIApplication.shared.delegate as! AppDelegate).saveContext()

    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        //have to remove or will crash
        incrementListTextView.removeObserver(self, forKeyPath: "contentSize")
    }

    /// Force the text in a UITextView to always center itself.
    func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutableRawPointer) {
        let textView = object as! UITextView
        var topCorrect = (textView.bounds.size.height - textView.contentSize.height * textView.zoomScale) / 2
        topCorrect = topCorrect < 0.0 ? 0.0 : topCorrect;
        textView.contentInset.top = topCorrect
    }



    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }



}

extension String {
    var html2AttributedString: NSAttributedString? {
        guard let data = data(using: .utf8) else { return nil }
        do {
            return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil)
        } catch let error as NSError {
            print(error.localizedDescription)
            return  nil
        }
    }
    var html2String: String {
        return html2AttributedString?.string ?? ""
    }
}

您好,我发现了您的问题并已在此处解决。

问题是你从来没有遵循过Swift 3 种风格的 KVO 方法调用 自 Swift 3 起,方法原型已更改。

您的代码在这里:

func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutableRawPointer) {
        let textView = object as! UITextView
        var topCorrect = (textView.bounds.size.height - textView.contentSize.height * textView.zoomScale) / 2
        topCorrect = topCorrect < 0.0 ? 0.0 : topCorrect;
        textView.contentInset.top = topCorrect
}

但新的原型如下:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        let textView = object as! UITextView
        var topCorrect = (textView.bounds.size.height - textView.contentSize.height * textView.zoomScale) / 2
        topCorrect = topCorrect < 0.0 ? 0.0 : topCorrect;
        textView.contentInset.top = topCorrect
    }

你看出区别了吗?

我还发现了另一个问题: 您确实在 viewWillAppear 函数中添加了观察者,请将其移至 viewDidLoad 函数,并将 removeObserver 代码移至 deinit 或覆盖 func didReceiveMemoryWarning()。

代码如下:

deinit {
        incrementListTextView.removeObserver(self, forKeyPath: "contentSize")
    }

希望对您有所帮助!