如何在非Objective-C类型上匹配Swift 4 KVO?

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

我有一个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()
        }
    }

}

在我的单元测试中,我需要确定何时在result实例中设置了APIDataResultContext。我不能使用KVO,因为我的Result<T>类型不能标记为dynamic,因为它无法在Objective-C中表示。

我不知道另一种方式可以让我监视result何时更改,而不是使用闭包属性或Notification,我不想这样做。不过,如果有必要的话,我会选择其中一个。

我可以通过哪些其他方式监控result的变化?

swift4 key-value-observing
2个回答
1
投票

我最终向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)
}

1
投票

你已经解决了这个问题(你所做的可能就是我所做的),但是为标题问题提供一个字面答案可能仍然有价值:如何在非Objective-C类型上使用KVO?

事实证明,这并不难,尽管有点难看。基本上,您需要创建一个类型为Any的Objective-C属性,其具有与Real属性的Swift名称相同的Objective-C名称。然后,将willSetdidSet处理程序放在realistic属性上,该属性为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,这意味着如果需要从对象外部观察,你就不能制作_resultKVO private,你将不得不弄乱你的班级与它的接口。但事实如此。

同样,我可能不会为你的特定用例做这个(如果你这样做,你显然可以在resultset中发出通知,而不是打扰willSetdidSet),但在某些情况下这可能是有用的,并且有一个答案描述如何作为参考,这是很好的。

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