Xcode 9.3.1 — iOS Swift 4.1 UITextView.becomeFirstResponder Thread 1: EXC_BAD_ACCESS (код = 1, адрес = 0x4d555458)

После обновления до Xcode 9.3.1 я столкнулся со сбоем Thread BAD_ACCESS, когда появляется клавиатура, вручную превращая UITextView в FirstResponder.

Чтобы было ясно, это раньше работало непосредственно перед обновлением 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()
        }
    }
}

Вот результат сбоя:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString isHidden]: unrecognized selector sent to instance 0x1d0224560'
*** First throw call stack:
(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: terminating with uncaught exception of type NSException

Этот сбой происходит независимо от того, использую ли я UITapGestureRecognizer в self.view или вручную касаюсь самого 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

person Chrishon Wyllie    schedule 17.05.2018    source источник
comment
Вы не снимаете наблюдение Notification?   -  person Larme    schedule 17.05.2018
comment
@Larme Удаление наблюдения также ничего не меняет. На самом деле, если я вообще закомментировал уведомление, сбой все равно происходит.   -  person Chrishon Wyllie    schedule 17.05.2018
comment
Попробуйте вызвать его из will/didAppear.   -  person CodeBender    schedule 17.05.2018
comment
Можете ли вы включить полную трассировку стека? Я не вижу ничего, что могло бы заставить blankTextView стать зомби, так что это может происходить внутри текстового представления.   -  person Brian Nickel♦    schedule 17.05.2018
comment
@matt Операторы UIView.animate необходимы для анимации NSLayoutConstraint с тем же интервалом времени, что и время анимации клавиатуры. Также вызов self.view.layoutIfNeeded позволяет плавно анимировать NSLayoutConstraint.   -  person Chrishon Wyllie    schedule 17.05.2018
comment
@BrianNickel Добавлена ​​полная трассировка стека по вашему запросу.   -  person Chrishon Wyllie    schedule 17.05.2018
comment
Для добавления реквизита Подключили вроде комментарий... Пробовали CLEAN? Это исправляет удивительное количество вещей.   -  person Terry    schedule 17.05.2018
comment
@ Терри, может быть, 100 раз на данный момент ... Я очень часто использовал такие реализации. Так уж получилось, что обновление Xcode 9.3.1 два дня назад нарушило эту несложную функцию.   -  person Chrishon Wyllie    schedule 17.05.2018
comment
@matt см. правку номер два   -  person Chrishon Wyllie    schedule 17.05.2018
comment
@matt Кстати, я установил начальный контроллер представления, как вы предложили в раскадровке (который практически пуст, кроме UIViewController по умолчанию), и это также не решило сбой. Я считаю, что предупреждение несколько неуместно для этого случая, так как я использую полностью программный подход.   -  person Chrishon Wyllie    schedule 17.05.2018
comment
@matt Я предполагаю, что ты имел в виду didFinishLaunchingWithOptions. В любом случае, я установил начальный контроллер представления, изменил настройку info.plist, но ничего не сделал для решения проблемы. Под исправлением didFinishLaunchingWithOptions что именно вы имели в виду?   -  person Chrishon Wyllie    schedule 17.05.2018
comment
Вы по-прежнему получаете сбой, если не слушаете уведомления клавиатуры? Является ли это частью beFirstResponder, которая дает сбой, или сбой происходит, когда вы анимируете свое ограничение?   -  person rodskagg    schedule 17.05.2018
comment
Это сбой на устройстве, симуляторе или на обоих? Я пытался использовать этого минимального делегата приложения, и я не получаю этот сбой в 8 или симулятор X с версией 11.3 (15E217)   -  person Brian Nickel♦    schedule 17.05.2018


Ответы (1)


Я разобрался....

Я хочу начать с того, что решение НЕ входит в объем вопроса, но я решил, что все равно должен опубликовать его, поскольку оно может неосознанно повлиять на другие элементы пользовательского интерфейса, которые кажутся связанными с ним, тем самым напрямую вызывая мою проблему.

Для начала в другом месте я определил расширение для 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, вызывающий эту расширенную функцию (не self.view UIViewController, а пользовательский UIView). У меня было требование, когда мне нужно было заменить этот подуровень другим CALayer, в основном начиная с нуля. Поэтому я очистил текущий, если он был.

Например, представьте, что я определил другой UIView с именем someBlankUIView. В этом UIView нет подвидов, но я вызываю для него эту функцию doSomething() следующим образом: someBlankUIView.doSomething() после добавления его в качестве подвида и ожидания вызова UIViewController viewDidLayoutSubviews(). Это НЕ вызывает немедленного сбоя и работает как задумано.

ОДНАКО, следующие действия приведут к сбою:
- как только вы заставите клавиатуру появиться с помощью becomFirstResponder()
- коснитесь UITextField/UITextView
- вызовите функцию, например layoutIfNeeded(), setNeedsLayout() и т. д., на self.view UIViewController

ИЗВЕСТНЫЕ СБОИ. Если оставить это удаление всех подуровней, следующее приведет к нескольким видам сбоев. Примеры включают:
EXC_BAD_ACCESS
NSArray position
om_wf isHidden
__NSCFString isHidden
-__NSOrderedSetM isHidden:

Следует отметить, что некоторые из них имеют одно и то же ключевое слово isHidden, но в то же время эти ошибки отображаются, по-видимому, случайным образом, поскольку код не меняется между сбоями, но в журнале отображается другая причина.

РЕШЕНИЕ:

Просто никогда не удалять слой из подслоев? Даже если вы вставили его вручную. Это звучит несколько необычно и, возможно, требует дополнительных исследований.

Но опять же, как я уже сказал, это решение может выходить за рамки первоначального вопроса, но я подумал, что должен опубликовать его, поскольку оно напрямую вызывает сбой.

ДОПОЛНИТЕЛЬНЫЕ ПРИМЕЧАНИЯ:

Также, чтобы уточнить, это, вероятно, не имеет ничего общего с обновлением Xcode 9.3.1. По опыту обновление всегда что-то ломает для меня, и на этот раз я предположил, что это снова так, поскольку я обновил сразу после добавления этой единственной строки кода. (Мне пришлось обновить Xcode, так как я обновил свой iPhone, что вызвало проблемы совместимости с моей текущей версией Xcode). Надеюсь, я был достаточно описательным.

person Chrishon Wyllie    schedule 17.05.2018
comment
Это помогло мне добраться до области моей проблемы! Я получил это, когда удалил все подслои в представлении, и это действительно вызвало эти сбои, которые было трудно определить. мое решение, однако, заключалось не в том, чтобы никогда не удалять подслои, а в том, чтобы удалять только те, которые я добавил. - person Pasosta; 29.03.2019