Kvo Listener Issues in Swift 4

KVO listener issues in Swift 4

The .observe(_:options:changeHandler:) function returns a NSKeyValueObservation object that is used to control the lifetime of the observation. When it is deinited or invalidated, the observation will stop.

Since your view controller doesn't keep a reference to the returned "observation" it goes out of scope at the end of viewDidLoad and thus stops observing.

To continue observing for the lifetime of the view controller, store the returned observation in a property. If you're "done" observing before that, you can call invalidate on the observation or set the property to nil.

Observer, Action Listener, KVO in iOS Swift

Something like this:

class A {   weak var observer : AnyObject?   var willChange: Int = 0{     didSet{          if let bObject = observer as? B{             bObject.whatEver()          }     }   }}
class B { let someThing = A() someThing.observer = self
func whatEver() { //called if willChange is changed ... }}

How Can I Match Swift 4 KVO on Non-Objective-C Type?

I ended up adding a closure property to 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?()
}
}

}

I use the closure in my tests to determine when result has been changed:

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

Swift 4 switch to new observe API

currentItem is an optional property of AVPlayer. The following compiles
in Swift 4.2/Xcode 10 (note the additional question mark in the key path):

let observer = player.observe(\.currentItem?.status, options: [.new]) {
(player, change) in
guard let optStatus = change.newValue else {
return // No new value provided by observer
}
if let status = optStatus {
// `status` is the new status, type is `AVPlayerItem.Status`
} else {
// New status is `nil`
}
}

The observed property is an optional AVPlayer.Status?, therefore
change.newValue inside the callback is a “double optional” AVPlayer.Status?? and must be unwrapped twice.

It may fail to compile in older Swift versions, compare
Swift’s ‘observe()’ doesn’t work for key paths with optionals? in the Swift forum.

How to use KVO for UserDefaults in Swift?

As of iOS 11 + Swift 4, the recommended way (according to SwiftLint) is using the block-based KVO API.

Example:

Let's say I have an integer value stored in my user defaults and it's called greetingsCount.

First I need to extend UserDefaults with a dynamic var that has the same name as the user defaults key you want to observe:

extension UserDefaults {
@objc dynamic var greetingsCount: Int {
return integer(forKey: "greetingsCount")
}
}

This allows us to later on define the key path for observing, like this:

var observer: NSKeyValueObservation?

init() {
observer = UserDefaults.standard.observe(\.greetingsCount, options: [.initial, .new], changeHandler: { (defaults, change) in
// your change logic here
})
}

And never forget to clean up:

deinit {
observer?.invalidate()
}

Trying to observe a string in UserDefaults, but struggling with compile errors

You can try this.

extension UserDefaults {
@objc dynamic var language: String {
get { self.string(forKey: "language") ?? "en" }
set { self.setValue(newValue, forKey: "language") }
}
}

class MyObject {
var observer: NSKeyValueObservation?

init() {
observer = UserDefaults.standard.observer(\.language, options: [.new], changeHandler: { (defaults, change) in
// your change logic here
})
}

deinit {
observer?.invalidate()
}
}


UPDATE

import Foundation

extension UserDefaults {
@objc dynamic var language: String {
get { self.string(forKey: #function) ?? "en" }
set { self.setValue(newValue, forKey: #function) }
}
}

class TopViewModel: NSObject {
let defaults = UserDefaults.standard
let languageKeyPath = #keyPath(UserDefaults.language)

override init() {
super.init()
defaults.addObserver(self, forKeyPath: languageKeyPath, options: .new, context: nil)

let language = defaults.language
print("initialLanguage: \(language)")

defaults.language = "en"
defaults.language = "fr"
}

deinit {
defaults.removeObserver(self, forKeyPath: languageKeyPath)
}

override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?) {
guard (object as? UserDefaults) === defaults,
keyPath == languageKeyPath,
let change = change
else { return }

if let updatedLanguage = change[.newKey] as? String {
print("updatedLanguage : \(updatedLanguage)")
}
}
}

// Test code, run init to observe changes
let viewModel = TopViewModel()


Related Topics



Leave a reply



Submit