Сбой блока Swift 4 KVO: наблюдаемый объект освобожден, пока наблюдатель все еще зарегистрирован - PullRequest
0 голосов
/ 27 апреля 2018

Я недавно начал разрабатывать свое приложение с iOS 11 в качестве целевой версии, потому что это было значение по умолчанию. Сейчас я снизил версию до 9.3 по причинам.

Приложение - чистый swift 4, с использованием нового блока блоков KVO. Я исправил несколько ошибок времени компиляции, которые у меня были с safeAreaInsets и еще много чего, и приложение успешно собралось. Быстрая работа. Ницца.

Я попытался запустить его на iPhone 7 iOS 10.3.1 Simulator, и, господин, это был крушение поезда. Я думаю, UITableViewAutomaticDimension не было вещью назад в те дни.

В любом случае, я исправил большинство проблем с макетом, но теперь я застрял с несколькими серьезными сбоями. Везде, где я использовал это новое KVO, он падает, когда я возвращаюсь назад. Мой ViewController с нажатой навигацией - KVO-прослушивание поля внутри объекта, который он содержит. Когда я открываю навигацию, viewController и объект освобождаются в указанном порядке, и приложение вылетает, выдавая мне эту ошибку:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7fdf2e724250 of class MyProject.MyObject was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x60800003fd80> (
<NSKeyValueObservance 0x610000050020: Observer: 0x61000006f140, Key path: isSelected, Options: <New: NO, Old: NO, Prior: NO> Context: 0x0, Property: 0x6180000595f0>
)'

Насколько я могу судить, он говорит, что наблюдаемый объект MyObject был освобожден, когда кто-то наблюдал переменную isSelected.

Это код наблюдения в MyViewController, который наблюдает MyObject.

var observations:[NSKeyValueObservation] = []
func someFunction() {
    observations.removeAll()
    let myObject = MyObject(/*init*/)
    self.myObject = myObject
    observations.append(myObject.observe(\.isSelected, changeHandler: { [weak self] (object, value) in
        //Do stuff
    }))
}

У меня сложилось впечатление, что этот новый волшебный стиль KVO-блока решит мир во всем мире, но, очевидно, это применимо только к iOS 11.

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

Поскольку журналы аварий показывают, что наблюдаемый объект освобождается, когда объект наблюдает за ним, но я также знаю, что наблюдаемый объект освобождается перед наблюдаемым объектом, я попытался сделать это в наблюдателе:

//In MyViewController
deinit {
    observations.forEach({$0.invalidate()})
    observations.removeAll()
    print("Observers removed")
}

Но это не помогает. Я также сделал это:

//In MyObject
deinit{
    print("MyObject deinit")
}

И когда я это делаю, я получаю следующий вывод:

>Observers removed
>MyObject deinit
>WORLD WAR 5 STACK TRACE

я тоже пробовал

//In MyViewController
deinit{
    self.myObject.removeObserver(self, forKeyPath: "isSelected")
}

Но я получаю вывод, говорящий Can't remove because it is not registered as an observer. Так что я думаю MyViewController не на самом деле наблюдатель, который привязывается при использовании этого нового KVO.

Почему ничего из этого не работало? Где и когда мне нужно удалить наблюдателей в

1 Ответ

0 голосов
/ 27 апреля 2018

Похоже, вы столкнулись с ошибкой, о которой я сообщил около года назад, но которой, к сожалению, уделялось очень мало внимания:

https://bugs.swift.org/browse/SR-5752

Поскольку эта ошибка меня давно не беспокоила, я надеялся, что она была исправлена ​​в оверлее Swift, но я просто попытался скопировать свой код из отчета об ошибках в проект iOS и запустить его в 10.3. .1 симулятор, и, конечно же, сбой вернулся.

Вы можете обойти это так:

deinit {
    for eachObservation in observations {
        if #available(/*whichever version of iOS fixes this*/) { /* do nothing */ } else {
            self.removeObserver(eachObservation, forKeyPath: #keyPath(/*the key path*/))
        }
        eachObservation.invalidate()
    }

    observations.removeAll()
}

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

...