SwiftUI - 不会触发 Combine 的 KV Observe 完成

SwiftUI - KV Observe completion from Combine does not get triggered

我正在尝试使用名为 VailerSIPLib 的库构建一个 VOIP 应用程序。由于库是使用 Obj-C 构建的,并且大量使用 NotificationCenter 来发布所有地方的活动状态更改。

我目前在项目的 CallView 部分,我可以设法开始、结束、拒绝来电。但是,我需要在视图中实现 connectionStatus,它将提供有关通话的信息​​,例如持续时间、"connecting.."、"disconnected"、"ringing" 等

以下代码全部在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 
}

问题:

它打印出 setCall(_:) 中除 completionBlock 之外的所有内容。 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 which I could not find anything related to it.

Finally there is yellow warning at the activeCall.observe(_:) says

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

这是在告诉你问题出在哪里。 observe(_:options:changeHandler:)方法只有incompletely documented。它 returns 一个 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: \([=13=])") }
    }

没有特定理由在您的示例代码中使用 Combine API,但通常 PublisherNSKeyValueObservation 更灵活,因为 Combine 提供了多种操作方式在 Publishers.


您的 addObserver(_:forKeyPath:options:context:) 错误是因为 API 的年龄要大得多。它早在 Swift 发明之前就被添加到 NSObject 中。事实上,它是在 Objective-C 甚至有块(闭包)之前添加的。当您使用该方法时,所有通知都会发送到观察者的 observeValue(forKeyPath:of:change:context:) 方法。如果您不实现 observeValue 方法,NSObject 中的默认实现会收到通知并引发异常。