依赖键路径的KVO对Swift类不起作用

问题描述 投票:3回答:1

我正在尝试在Swift中编写一个围绕URLSessionTask的包装器。根据to the documentation

所有任务属性都支持键值观察。

所以我想保持这种行为并使我的包装器上的所有属性也符合KVO(通常委托给包装的任务)并且完全可以访问Objective-C。我将描述我正在使用一个属性做什么,但我基本上想对所有属性做同样的事情。

我们来看看stateURLSessionTask。我像这样创建我的包装器:

@objc(MyURLSessionTask)
public class TaskWrapper: NSObject {
    @objc public internal(set) var underlyingTask: URLSessionTask?
    @objc dynamic public var state: URLSessionTask.State {
        return underlyingTask?.state ?? backupState
    }
    // the state to be used when we don't have an underlyingTask
    @objc dynamic private var backupState: URLSessionTask.State = .suspended

    @objc public func resume() {
        if let task = underlyingTask {
            task.resume()
            return
        }
        dispatchOnBackgroundQueue {
            let task:URLSessionTask = constructTask()
            task.resume()
            self.underlyingTask = task
        }
    }
}

我将@objc添加到属性中,以便可以从Objective-C调用它们。我将dynamic添加到属性中,因此它们将通过消息传递/运行时甚至从Swift调用,以确保NSObject可以生成正确的KVO通知。根据Apple's KVO chapter in the "Using Swift with Cocoa and Objective-C" book,这应该是足够的。

然后我实现了静态类方法necessary to tell KVO about dependent key paths

// MARK: KVO Support
extension TaskWrapper {
    @objc static var keyPathsForValuesAffectingState:Set<String> {
        let keypaths:Set<String> = [
            #keyPath(TaskWrapper.backupState),
            #keyPath(TaskWrapper.underlyingTask.state)
        ]
        return keypaths
    }
}

然后我写了一个单元测试来检查通知是否被正确调用:

var swiftKVOObserver:NSKeyValueObservation?

func testStateObservation() {
    let taskWrapper = TaskWrapper()
    let objcKVOExpectation = keyValueObservingExpectation(for: taskWrapper, keyPath: #keyPath(TaskWrapper.state), handler: nil)
    let swiftKVOExpectation = expectation(description: "Expect Swift KVO call for `state`-change")
    swiftKVOObserver = taskWrapper.observe(\.state) { (_, _) in
        swiftKVOExpectation.fulfill()
    }
    // this should trigger both KVO versions
    taskWrapper.underlyingTask = URLSession(configuration: .default).dataTask(with: url)
    self.wait(for: [swiftKVOExpectation, objcKVOExpectation], timeout: 0.1)
}

当我运行它时,测试与NSInternalInconsistencyException崩溃:

***由于未捕获的异常'NSInternalInconsistencyException'而终止应用程序,原因:'无法从<MyURLSessionTask 0x6000002a1440>中删除关键路径“underlyingTask.state”的观察者<_XCKVOExpectationImplementation 0x60000009d6a0>,很可能是因为键“underlyingTask”的值在没有发送适当的KVO通知的情况下已更改。检查MyURLSessionTask类的KVO兼容性。

但是通过制作underlyingTask-property @objcdynamic,Objective-C运行时应确保发送此通知,即使任务从Swift更改,对吧?

我可以通过手动发送基础任务的KVO通知来正确地进行测试,如下所示:

@objc public internal(set) var underlyingTask: URLSessionTask? {
    willSet {
        willChangeValue(for: \.underlyingTask)
    }
    didSet {
        didChangeValue(for: \.underlyingTask)
    }
}

但我宁愿避免为每个属性实现这个,并且更愿意使用现有的keyPathsForValuesAffecting<Key>方法。我错过了一些让这项工作的东西吗?或者它应该工作,这是一个错误?

swift dynamic key-value-observing objective-c-swift-bridge
1个回答
1
投票

物业underlyingTask不是dynamic

© www.soinside.com 2019 - 2024. All rights reserved.