首页 > 解决方案 > Xcode 9.3.1 - iOS Swift 4.1 UITextView.becomeFirstResponder 线程 1:EXC_BAD_ACCESS(代码=1,地址=0x4d555458)

问题描述

更新到 Xcode 9.3.1 后,当通过手动使 UITextView 成为FirstResponder 出现键盘时,我遇到了线程 BAD_ACCESS 崩溃。

需要明确的是,这在将 Xcode 更新到 9.3.1 之前就已经有效,但现在我无法弄清楚为什么会发生崩溃。

我做了一个精简的例子来展示这一点。我提供的 UIViewController 中没有其他变量或函数。

class ErrorTestsController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        setupUIElements()
    }

    private func setupUIElements() {
        view.backgroundColor = UIColor.red
        view.addSubview(blankTextView)

        blankTextView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        blankTextView.widthAnchor.constraint(equalToConstant: 200.0).isActive = true
        blankTextView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true

        blankTextViewBottomAnchor = blankTextView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0.0)
        blankTextViewBottomAnchor?.isActive = true
        // Whether I use safeAreaLayoutGuide or the regular view's bottomAnchor changes nothing

        // Add observers to move the blankTextView up when the keyboard appears
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)

        // add a UITapGestureRecognizer to make the blankTextView becomeFirstResponder and show the keyboard (will also trigger the above NotificationObserver)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(showAddTextView)))
    }

    // Define reference to bottomAnchor of the UITextView called "blankTextView" (using iOS 9.0 + NSLayoutConstraints)
    public var blankTextViewBottomAnchor: NSLayoutConstraint?

    public var blankTextView: UITextView = {
        let textview = UITextView(frame: .zero, textContainer: nil)
        textview.translatesAutoresizingMaskIntoConstraints = false
        textview.backgroundColor = UIColor.black
        return textview
    }()

    @objc private func showAddTextView() {
        blankTextView.becomeFirstResponder()
    }

    @objc private func keyboardWillShow(notification: NSNotification) {
        guard
            let keyboardFrame = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue,
            let keyboardDuration = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue
            else { return }

        blankTextViewBottomAnchor?.constant = -keyboardFrame.height

        UIView.animate(withDuration: keyboardDuration) {
            self.view.layoutIfNeeded()
        }
    }

    @objc private func keyboardWillHide(notification: NSNotification) {
        guard
            let keyboardDuration = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue
            else { return }

        blankTextViewBottomAnchor?.constant = 0.0

        UIView.animate(withDuration: keyboardDuration) { 
            self.view.layoutIfNeeded()
        }
    }
}

这是崩溃输出:

*** 由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“-[__NSCFString isHidden]:无法识别的选择器发送到实例 0x1d0224560”
*** 首先抛出调用堆栈:
(0x181c76d8c 0x180e305ec 0x181c84098 0x181c7c5c8 0x181b6241c 0x18b8351bc 0x18bdb588c 0x18b9c1310 0x18b9c132c 0x18b9c132c 0x18b9c0eec 0x18b9c0850 0x18b9b9934 0x18b84f30c 0x18b9b0330 0x18b8f1b30 0x18b8f1f4c 0x18bac6304 0x100979324 0x100979368 0x18ba26750 0x18bf932a4 0x18bb88e6c 0x18ba257a8 0x18bf84ac4 0x18ba1f540 0x18ba1f078 0x18ba1e8dc 0x18ba1d238 0x18c1fec0c 0x18c2011b8 0x18c201518 0x18c1fa258 0x181c1f404 0x181c1ec2c 0x181c1c79c 0x181b3cda8 0x183b1f020 0x18bb1d78c 0x10089e748 0x1815cdfc0)
libc++abi.dylib:以 NSException 类型的未捕获异常终止

无论我在 self.view 上使用 UITapGestureRecognizer 还是手动点击 UITextView 本身,都会发生这种崩溃。两者都会导致相同的崩溃。

注意:在某些情况下,我观​​察到实际的崩溃输出是不同的。在这种情况下,它显示“[__NSCFString isHidden]:”。在其他情况下,输出为“[__NSArrayI position]:”。但是,我没有随时更改代码。

但它总是某种形式的 EXC_BAD_ACCESS。

编辑:全栈跟踪

2018-05-17 12:41:23.714232-0400 TESTAPP[4368:1363924] [Application] Failed to instantiate the default view controller for UIMainStoryboardFile 'Main' - perhaps the designated entry point is not set?

2018-05-17 12:41:25.497939-0400 TESTAPP[4368:1363924] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2018-05-17 12:41:25.500096-0400 TESTAPP[4368:1363924] [MC] Reading from public effective user settings.
2018-05-17 12:41:25.514580-0400 TESTAPP[4368:1363924] -[__NSCFString isHidden]: unrecognized selector sent to instance 0x1d023ec00
2018-05-17 12:41:25.515767-0400 TESTAPP[4368:1363924] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString isHidden]: unrecognized selector sent to instance 0x1d023ec00'
*** First throw call stack:
(0x181c76d8c 0x180e305ec 0x181c84098 0x181c7c5c8 0x181b6241c 0x18b8351bc 0x18bdb588c 0x18b9c1310 0x18b9c132c 0x18b9c132c 0x18b9c0eec 0x18b9c0850 0x18b9b9934 0x18b84f30c 0x18b9b0330 0x18b8f1b30 0x18b8f1f4c 0x18bac6304 0x100fa58cc 0x100fa5910 0x18ba26750 0x18bf932a4 0x18bb88e6c 0x18ba257a8 0x18bf84ac4 0x18ba1f540 0x18ba1f078 0x18ba1e8dc 0x18ba1d238 0x18c1fec0c 0x18c2011b8 0x18c201518 0x18c1fa258 0x181c1f404 0x181c1ec2c 0x181c1c79c 0x181b3cda8 0x183b1f020 0x18bb1d78c 0x100ecad04 0x1815cdfc0)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 

编辑2:这都是以编程方式完成的。不使用故事板。在我的 AppDelegate 中,我定义了 UIWindow 及其 rootView,如下所示:

    window = UIWindow(frame: UIScreen.main.bounds)
    window?.makeKeyAndVisible()

    let navigationController = UINavigationController(rootViewController: ErrorTestsController())
    window?.rootViewController = navigationController

标签: iosswiftuitextviewexc-bad-accessbecomefirstresponder

解决方案


我已经想通了......

我想首先说解决方案不在问题的范围内,但我认为无论如何我都应该发布它,因为它可能会在不知不觉中影响似乎与之相关的其他 UI 元素,从而直接导致我的问题。

首先,在其他地方,我为 UIView 定义了一个扩展,如下所示......

extension UIView {

    func doSomething() {

        // Attempt to be sure that sublayers exist first...
        if (self.layer.sublayers != nil) && (self.layer.sublayers.count > 0) {

            self.layer.sublayers?.removeAll() 
            // These also cause crash
            // self.layer.sublayers?.removeFirst() 
            // self.layer.sublayers?.forEach { $0.removeFromSuperlayer() } 
            // self.layer.sublayers = nil
        }

        // I insert a CALayer at index 0 with:
        self.layer.insertSublayer(someCALayer, at: 0)
    }
}

解释:

在某些时候,我手动将 CALayer 添加到调用此扩展函数的 UIView 中(不是 UIViewController 的 self.view,而是自定义 UIView)。我有一个要求,我需要用另一个 CALayer 替换这个子层,基本上是从头开始。所以我清除了当前的一个,如果有的话。

例如,假设我定义了另一个 UIView,名为someBlankUIView. 这个 UIView 里面没有子视图,但我这样调用这个doSomething()函数:someBlankUIView.doSomething()在将它添加为子视图并等待 UIViewController 调用之后viewDidLayoutSubviews()。这不会立即导致崩溃并按预期工作。

但是,以下操作将导致崩溃:
- 一旦您使键盘出现becomFirstResponder()
- 点击 UITextField/UITextView -在 UIViewController上
调用类似layoutIfNeeded(),setNeedsLayout()等的函数self.view

已知崩溃: 如果保留所有子层的删除,以下将导致多种崩溃。示例包括:
EXC_BAD_ACCESS
NSArrayI 位置
om_wf isHidden
__NSCFString isHidden
-__NSOrderedSetM isHidden:

必须注意,其中一些具有相同的isHidden关键字,但同时,这些错误似乎是随机显示的,因为代码在崩溃之间没有变化,但在日志中显示了不同的原因。

解决方案:

永远不要从子层中删除层?即使您手动插入了一个。这听起来有些不寻常,也许需要更多的研究。

但是就像我说的那样,这个解决方案可能超出了原始问题的范围,但我认为我应该发布它,因为它直接导致崩溃。

进一步说明:

还要澄清一下,这可能与更新 Xcode 9.3.1 无关。根据经验,更新总是对我造成破坏,这一次,我认为又是这种情况,因为我在添加这一行代码后立即进行了更新。(我必须更新 Xcode,因为我更新了我的 iPhone,这导致了与我当时的 Xcode 版本的兼容性问题)。希望我的描述性足够。


推荐阅读