2017-12-13 4 views
2

Ich habe einen Result Typen, den ich in asynchronen Prozessen verwenden:Wie kann ich Swift 4 KVO auf Nicht-Objective-C-Typ anpassen?

internal enum Result<T> { 

    case success(T) 

    case failure(Error) 

} 

Ich habe auch ein APIDataResultContext, dass ich Daten zwischen Operation Subklassen passieren verwenden:

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() 
     } 
    } 

} 

In meinen Unit-Tests, ich brauche um zu bestimmen, wann result in eine APIDataResultContext Instanz gesetzt wurde. Ich kann KVO nicht verwenden, weil mein Result<T> Typ nicht als dynamic markiert werden kann, da es in Objective-C nicht dargestellt werden kann.

Ich weiß nicht, auf eine andere Weise, die mir erlauben wird, zu überwachen, wenn result geändert wird, außer mit einer Verschlusseigenschaft oder Notification, die ich lieber nicht tun würde. Ich werde jedoch bei Bedarf auf eines der beiden zurückgreifen.

Welche andere (n) Weise (n) kann ich für einen Wechsel von result überwachen?

Antwort

0

Ich landete das Hinzufügen einer Schließung Eigenschaft 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?() 
     } 
    } 

}

ich die Schließung in meinen Tests verwenden, um festzustellen, wann result wurde geändert:

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) 
} 
0

Sie‘ Ich habe dieses Problem bereits gelöst (und was Sie getan haben ist wahrscheinlich, was ich tun würde), aber es gibt wahrscheinlich noch immer einen Wert, der eine wörtliche Antwort auf die Titelfrage liefert: Wie kann Sie KVO auf einem Nicht-Objective-C-Typ verwenden?

Wie sich herausstellt, ist es nicht zu schwierig, obwohl es etwas hässlich ist. Im Grunde müssen Sie eine Objective-C-Eigenschaft erstellen, die Any mit demselben Objective-C-Namen wie dem Swift-Namen der Real-Eigenschaft eingegeben wird. Dann setzen Sie willSet und didSet Handler auf die Real-Eigenschaft, die die entsprechenden KVO-Methoden für die Objective-C-Eigenschaft aufrufen. Also, so etwas wie:

@objc(result) private var _resultKVO: Any { return self.result } 
internal var result: Result<Data>! { 
    willSet { self.willChangeValue(for: \._resultKVO) } 
    didSet { self.didChangeValue(for: \._resultKVO) } 
} 

(Aus Gründen der Einfachheit, ich gehe davon aus, dass result Ihre gespeicherten Eigenschaft ist, und das Entfernen der Sperre und das Privateigentum aus der Gleichung)

Der Nachteil ist, dass Sie müssen _resultKVO anstelle von result verwenden, wenn Sie Schlüsselpfade zum Beobachten konstruieren, was bedeutet, dass Sie _resultKVOprivate nicht machen müssen, wenn dies von außerhalb des Objekts beobachtbar sein muss, und Sie die Schnittstelle Ihrer Klasse damit überladen müssen es. Aber so geht es.

Noch einmal, ich würde wahrscheinlich tut dies nicht für Ihren speziellen Anwendungsfall (und wenn ja, könnten Sie natürlich die Benachrichtigungen in result ‚s Feuer set statt mit willSet und didSet kümmern zu müssen), aber in einigen Fällen das kann nützlich sein, und es ist gut, eine Antwort zu haben, die beschreibt, wie man es als Referenz macht.