如何在非 Objective-C 类型上匹配 Swift 4 KVO?
How Can I Match Swift 4 KVO on Non-Objective-C Type?
我有一个在异步进程中使用的 Result
类型:
internal enum Result<T> {
case success(T)
case failure(Error)
}
我还有一个 APIDataResultContext
用于在 Operation
子类之间传递数据:
internal final class APIDataResultContext: NSObject {
// MARK: Properties
private let lock = NSLock()
private var _result: Result<Data>!
internal var result: Result<Data>! {
get {
lock.lock()
let temp = _result
lock.unlock()
return temp
}
set {
lock.lock()
_result = newValue
lock.unlock()
}
}
}
在我的单元测试中,我需要确定何时在 APIDataResultContext
实例中设置了 result
。我不能使用 KVO,因为我的 Result<T>
类型不能标记为 dynamic
因为它不能在 Objective-C 中表示。
除了使用闭包 属性 或 Notification
之外,我不知道还有什么其他方法可以让我监控何时更改 result
,我不希望这样做去做。不过,如果有必要,我会使用两者之一。
我可以通过哪些其他方式监控 result
的变化?
我最后添加了一个闭包 属性 到 APIDataResultContext
:
internal final class APIDataResultContext {
// MARK: Properties
internal var resultChanged: (()->())?
private let lock = NSLock()
private var _result: Result<Data>!
internal var result: Result<Data>! {
get {
lock.lock()
let temp = _result
lock.unlock()
return temp
}
set {
lock.lock()
_result = newValue
lock.unlock()
resultChanged?()
}
}
}
我在测试中使用闭包来确定 result
何时被更改:
internal func testNeoWsFeedOperationWithDatesPassesDataToResultContext() {
let operationExpectation = expectation(description: #function)
let testData = DataUtility().data(from: "Hello, world!")
let mockSession = MockURLSession()
let testContext = APIDataResultContext()
testContext.resultChanged = {
operationExpectation.fulfill()
guard let result = testContext.result else {
XCTFail("Expected result")
return
}
switch result {
case .failure(_):
XCTFail("Expected data")
case .success(let data):
XCTAssertEqual(data, testData, "Expected '\(testData)'")
}
}
NeoWsFeedOperation(context: testContext, sessionType: mockSession, apiKey: testAPIKey, startDate: testDate, endDate: testDate).start()
mockSession.completionHandler?(testData, nil, nil)
wait(for: [operationExpectation], timeout: 2)
}
你已经解决了这个问题(你所做的可能就是我要做的),但为标题问题提供字面答案可能仍然有价值:如何 你在 non-Objective-C 类型上使用 KVO?
事实证明,做起来并不难,虽然有点难看。基本上,您需要创建一个类型为 Any
的 Objective-C 属性,其 Objective-C 名称与真实 属性 的 Swift 名称相同.然后,将 willSet
和 didSet
处理程序放在真正的 属性 上,为 Objective-C 属性 调用适当的 KVO 方法。所以,像这样:
@objc(result) private var _resultKVO: Any { return self.result }
internal var result: Result<Data>! {
willSet { self.willChangeValue(for: \._resultKVO) }
didSet { self.didChangeValue(for: \._resultKVO) }
}
(为了简单起见,我假设 result
是您存储的 属性,并从等式中删除锁和私有 属性)
需要注意的是,在构建要观察的关键路径时,您必须使用 _resultKVO
而不是 result
,这意味着如果需要从 object 外部观察,你不能制作 _resultKVO
private
,你将不得不用它弄乱 class 的界面。但事情就是这样。
同样,我可能不会针对您的特定用例执行此操作(如果您这样做,您显然可以在 result
的 set
中触发通知,而不必费心willSet
和 didSet
),但在某些情况下这可能很有用,最好有一个描述如何做的答案作为参考。
我有一个在异步进程中使用的 Result
类型:
internal enum Result<T> {
case success(T)
case failure(Error)
}
我还有一个 APIDataResultContext
用于在 Operation
子类之间传递数据:
internal final class APIDataResultContext: NSObject {
// MARK: Properties
private let lock = NSLock()
private var _result: Result<Data>!
internal var result: Result<Data>! {
get {
lock.lock()
let temp = _result
lock.unlock()
return temp
}
set {
lock.lock()
_result = newValue
lock.unlock()
}
}
}
在我的单元测试中,我需要确定何时在 APIDataResultContext
实例中设置了 result
。我不能使用 KVO,因为我的 Result<T>
类型不能标记为 dynamic
因为它不能在 Objective-C 中表示。
除了使用闭包 属性 或 Notification
之外,我不知道还有什么其他方法可以让我监控何时更改 result
,我不希望这样做去做。不过,如果有必要,我会使用两者之一。
我可以通过哪些其他方式监控 result
的变化?
我最后添加了一个闭包 属性 到 APIDataResultContext
:
internal final class APIDataResultContext {
// MARK: Properties
internal var resultChanged: (()->())?
private let lock = NSLock()
private var _result: Result<Data>!
internal var result: Result<Data>! {
get {
lock.lock()
let temp = _result
lock.unlock()
return temp
}
set {
lock.lock()
_result = newValue
lock.unlock()
resultChanged?()
}
}
}
我在测试中使用闭包来确定 result
何时被更改:
internal func testNeoWsFeedOperationWithDatesPassesDataToResultContext() {
let operationExpectation = expectation(description: #function)
let testData = DataUtility().data(from: "Hello, world!")
let mockSession = MockURLSession()
let testContext = APIDataResultContext()
testContext.resultChanged = {
operationExpectation.fulfill()
guard let result = testContext.result else {
XCTFail("Expected result")
return
}
switch result {
case .failure(_):
XCTFail("Expected data")
case .success(let data):
XCTAssertEqual(data, testData, "Expected '\(testData)'")
}
}
NeoWsFeedOperation(context: testContext, sessionType: mockSession, apiKey: testAPIKey, startDate: testDate, endDate: testDate).start()
mockSession.completionHandler?(testData, nil, nil)
wait(for: [operationExpectation], timeout: 2)
}
你已经解决了这个问题(你所做的可能就是我要做的),但为标题问题提供字面答案可能仍然有价值:如何 你在 non-Objective-C 类型上使用 KVO?
事实证明,做起来并不难,虽然有点难看。基本上,您需要创建一个类型为 Any
的 Objective-C 属性,其 Objective-C 名称与真实 属性 的 Swift 名称相同.然后,将 willSet
和 didSet
处理程序放在真正的 属性 上,为 Objective-C 属性 调用适当的 KVO 方法。所以,像这样:
@objc(result) private var _resultKVO: Any { return self.result }
internal var result: Result<Data>! {
willSet { self.willChangeValue(for: \._resultKVO) }
didSet { self.didChangeValue(for: \._resultKVO) }
}
(为了简单起见,我假设 result
是您存储的 属性,并从等式中删除锁和私有 属性)
需要注意的是,在构建要观察的关键路径时,您必须使用 _resultKVO
而不是 result
,这意味着如果需要从 object 外部观察,你不能制作 _resultKVO
private
,你将不得不用它弄乱 class 的界面。但事情就是这样。
同样,我可能不会针对您的特定用例执行此操作(如果您这样做,您显然可以在 result
的 set
中触发通知,而不必费心willSet
和 didSet
),但在某些情况下这可能很有用,最好有一个描述如何做的答案作为参考。