我想写封信
URLSessionTask
在Swift中。相符合的
to the documentation
所有任务属性都支持键值观察。
因此,我希望保持这种行为,并使包装器上的所有属性也符合KVO(通常委托给包装的任务),并且完全可访问Objective-C。我将描述我对一个属性所做的操作,但我基本上希望对所有属性都做同样的操作。
我们拿着房子
state
属于
URLSessionTask
. 我创建的包装如下:
@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调用属性,以确保可以通过以下方式生成正确的KVO通知
NSObject
. 这应该足够了
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
:
***由于未捕获的异常“nSinternalinconsistenceexception”,正在终止应用程序,原因:“无法删除观察者<_XCKVOExpectationImplementation 0x60000009d6a0>对于键路径“underlyngtask.state”,来自(<MyURLSessionTask 0x6000002a1440>,很可能是因为键“underlyngtask”的值已更改,但没有发送适当的KVO通知。检查MyURLSessionTask类的KVO兼容性。”
但是通过
underlyingTask
-财产
@objc公司
和
动态
,Objective-C运行时应确保发送此通知,即使任务已从Swift更改,对吗?
通过手动发送UnderlyngTask的KVO通知,我可以使测试正常工作,如下所示:
@objc public internal(set) var underlyingTask: URLSessionTask? {
willSet {
willChangeValue(for: \.underlyingTask)
}
didSet {
didChangeValue(for: \.underlyingTask)
}
}
但我宁愿避免对每一个属性都实现这一点,而宁愿使用现有的
keyPathsForValuesAffecting<Key>
方法。我是不是错过了让这一切成功的东西?还是应该工作,这是一个bug?