我试图在Swift中围绕URLSessionTask
编写一个包装器。根据{{3}}
所有任务属性都支持键值观察。
所以我想保持这种行为并使我的包装器上的所有属性也符合KVO(通常委托给包装的任务)并且完全可以访问Objective-C。我将描述我对一个属性所做的事情,但我基本上想要对所有属性做同样的事情。
我们取URLSessionTask
的{{3}}财产。我像这样创建我的包装器:
@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通知。根据{{3}},这应该足够了。
然后我实现了静态类方法to the documentation:
// 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
:
***由于未捕获的异常终止应用程序&#39; NSInternalInconsistencyException&#39;,原因:&#39;无法删除观察者&lt; _XCKVOExpectationImplementation 0x60000009d6a0&gt;对于关键路径&#34; underlyingTask.state&#34;来自&lt; MyURLSessionTask 0x6000002a1440&gt;,很可能是因为key&#34; underlyingTask&#34;的值在没有发送适当的KVO通知的情况下已更改。检查MyURLSessionTask类的KVO合规性。&#39;
但是通过创建underlyingTask
- 属性@objc
和dynamic
,Objective-C运行时应该确保发送此通知,即使从Swift更改了任务,对吧?
我可以通过手动发送基础任务的KVO通知来正确地进行测试:
@objc public internal(set) var underlyingTask: URLSessionTask? {
willSet {
willChangeValue(for: \.underlyingTask)
}
didSet {
didChangeValue(for: \.underlyingTask)
}
}
但我宁愿避免为每个属性实现此功能,而更愿意使用现有的keyPathsForValuesAffecting<Key>
方法。我错过了一些让这项工作的东西吗?或者它应该工作,这是一个错误?
答案 0 :(得分:1)
财产underlyingTask
不是dynamic
。