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
How to Stretch a View to Its Parent Frame with Swiftui
Swiftui Navigationview Not See Image
Can't Dismiss View Controller That's Embedded in a Navigation Controller
Support Arkit in Lower End Devices
Animate UIlabel Width with Fixed Center
Actions Assigned to Nsmenuitem Dont Seem to Work
Swift: Object Instance by Name
Calculate Range of String from Word to End of String in Swift
Communicate Data Between Watchos & Today Extension Widget
Occasional Blank Frames After Exporting Asset - Avexportsession
Swiftui: Unable to Animate Images
How to Replace My .Xib File with Pure Swift 3
Why Do I Get Rgb Values of (0,0,0) for an Nsimage with a Transparent Background
Swift Compile Error, Subclassing Nsvalue, Using Super.Init(Nonretainedobject:)
Extension for Average to Return Double from Numeric Generic
Prevent Retain Cycle in Swift Function Pointers
Aws Cognito Credentialsprovider.Login Always Shows Nil (Swift)