SwiftUI - KV Наблюдать за завершением из Combine не запускается

Я пытаюсь создать приложение VOIP, используя библиотеку с именем VailerSIPLib. Поскольку библиотека была построена с использованием Obj-C и интенсивным использованием NotificationCenter для публикации изменений, активные состояния повсюду.

В настоящее время я нахожусь в CallView части проекта, я могу начинать, заканчивать, отклонять звонки. Однако мне нужно реализовать connectionStatus в представлении, которое будет предоставлять информацию о вызове, такую ​​как продолжительность, «соединение ..», «отключено», «звонки» и т. Д.

Код ниже находится в CallViewModel: ObservableObject;

Переменные:

var activeCall: VSLCall!
@Published var connectionStatus: String = ""

Инициализатор:

override init(){
        super.init()
        NotificationCenter.default.addObserver(self, selector: #selector(self.listen(_:)), name: Notification.Name.VSLCallStateChanged, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateInboundCallAccepted, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateOutboundCallStarted, object: nil)
    }

Методы:

func setCall(_ call: VSLCall) {
    self.activeCall = call
    self.activeCall.observe(\.callStateText) { (asd, change) in
        print("observing")
        print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
    } 
}

@objc func listen(_ notification: Notification) {
       if let _ = self.activeCall {
           print(self.activeCall.callStateText)
       }    
}

@objc func buildCallView(_ notification: Notification) {
    print("inbound call")
    self.isOnCall = true 
}

Проблема:

Он распечатывает все, кроме completionBlock в setCall(_:). Функция listen(_:) проверяет, что состояние activeCall меняется, и я хотел бы использовать это напрямую, однако она не всегда работает правильно. Он должен срабатывать, когда на вызов отвечает callState значение .confirmed, но иногда это происходит. Так я узнаю, что пора запустить таймер.

Другой момент в том, что в примере проекта VialerSIPLib они использовали self.activeCall.addObserver(_:), и он отлично работает. Проблема заключается в том, что метод выдает ошибку времени выполнения, например, didObservedValueChange(_:), и регистрирует An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.

Наконец, есть желтое предупреждение на activeCall.observe(_:) говорит

Result of call to 'observe(_:options:changeHandler:)' is unused я не смог найти ничего связанного с этим.


person Faruk    schedule 05.03.2020    source источник


Ответы (1)


Наконец, в activeCall есть желтое предупреждение. Observe (_ :) говорит

Result of call to 'observe(_:options:changeHandler:)'

Это говорит вам, в чем проблема. observe(_:options:changeHandler:) метод только не полностью документирован. Он возвращает объект типа NSKeyValueObservation, который представляет вашу регистрацию в качестве наблюдателя значения ключа. Вам необходимо сохранить этот объект, потому что когда NSKeyValueObservation уничтожается, он отменяет регистрацию. Итак, вам нужно добавить свойство в CallViewModel, чтобы сохранить его:

class CallViewModel: ObservableObject {
    private var callStateTextObservation: NSKeyValueObservation?
    ...

И тогда вам нужно сохранить наблюдение:

func setCall(_ call: VSLCall) {
    activeCall = call
    callStateTextObservation = activeCall.observe(\.callStateText) { _, change in
        print("observing")
        print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
    } 
}

Вместо этого вы можете использовать Combine API для KVO, хотя он еще менее документирован, чем Foundation API. Вы получаете Publisher, вывод которого - каждое новое значение наблюдаемого свойства. Работает это так:

class CallViewModel: ObservableObject {
    private var callStateTextTicket: AnyCancellable?
    ...

    func setCall(_ call: VSLCall) {
        activeCall = call
        callStateTextTicket = self.activeCall.publisher(for: \.callStateText, options: [])
            .sink { print("callId: \(call.callId), callStateText: \($0)") }
    }

Нет особой причины использовать Combine API в вашем примере кода, но в целом Publisher более гибок, чем NSKeyValueObservation, потому что Combine предоставляет так много способов работы с Publishers.


Ваша ошибка с addObserver(_:forKeyPath:options:context:) возникает из-за того, что это гораздо более старый API. Он был добавлен в NSObject задолго до изобретения Swift. Фактически, он был добавлен до того, как в Objective-C даже появились блоки (замыкания). Когда вы используете этот метод, все уведомления отправляются observeValue(forKeyPath:of:change:context:) методу наблюдателя. Если вы не реализуете метод observeValue, реализация по умолчанию в NSObject получит уведомление и вызовет исключение.

person rob mayoff    schedule 06.03.2020
comment
Спасибо за ответ, я нашел первое решение в каком-то блоге, и оно работает. Я отвечал, но вы сначала сделали это более пояснительно, чем я. Однако, как я нашел вас, у меня есть еще два вопроса. В библиотеке есть setCallState(_:) метод, который выполняет эту операцию. Он имеет willChangeValueForKey() вверху и didChangeValueForKey. Ниже willChangeValueForKey находится регистратор, который указывает что-то вроде callState will be from A to B.. Сразу после этого мой журнал в observe completion работает, и он говорит Call state is changed to A. Почему так происходит? - person Faruk; 06.03.2020
comment
Они реализовали эту функцию, используя observeValue, и она работает, но для меня это не имеет никакого смысла. Во-вторых, есть ли способ справиться с NotificationCenter.default.addObserver Combine способом? Я искал решение этого, но пока не повезло. И, наконец, в качестве примечания. Несмотря на то, что я реализовал метод observeValue(forKeyPath:of:change:context:) для своего класса, он снова выдавал связанную ошибку. @rob mayoff - person Faruk; 06.03.2020
comment
Что касается вашего первого вопроса, я не могу сказать, что происходит, не изучив реальный код. Вы можете опубликовать новый вопрос с дополнительной информацией, если хотите, но я не могу обещать ответить на него. Что касается вашего второго вопроса, вы ищете publisher(for:object:) метод NotificationCenter. Что касается вашего observeValue метода, я предполагаю, что вы звоните super.observeValue и не должны. - person rob mayoff; 06.03.2020
comment
Хорошо, я сделаю это, если не смогу решить эту проблему. Наконец, я попробовал оба, и мне не повезло, но я думаю, что больше никогда не буду использовать этот метод. Еще раз спасибо за интересный ответ :) - person Faruk; 06.03.2020