Perhaps this question is more general than I will make it seem, but I wanted to make sure I showed my full context in case something there is the cause of this issue.
I wrote a singleton class with a KVC-compliant property and two methods:
class Singleton: NSObject {
static let sharedInstance = Singleton()
#objc dynamic var aProperty = false
func updateDoesntWork() {
aProperty = !aProperty
}
func updateDoesWork() {
Singleton.sharedInstance.aProperty = !aProperty
}
}
I add an observer for the property in my app delegate's setup code:
Singleton.sharedInstance.addObserver(self, forKeyPath: #keyPath(Singleton.aProperty), options: [.new], context: nil)
I override my app delegate's observeValue() method:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
NSLog("observeValue(forKeyPath: \(String(describing:keyPath)), of: \(String(describing:object)), change: \(String(describing:change)), context:\(String(describing:context)))")
}
Now, if I call Singleton.sharedInstance.updateDoesntWork(), I don't get a log entry for the change in aProperty. The property is changed (I verified this in the debugger), it's just that no notification is sent.
Whereas, if I call Singleton.sharedInstance.updateDoesWork(), everything works as I would expect -- the property is also changed, of course, but most importantly, this time the observer is notified of the change (the log entry is printed).
It makes no sense to me that I should need the full Singleton.sharedInstance.aProperty rather than just aProperty for KVO to work. What am I missing?
I assume you have trouble to use "var" for a singleton. You may consider use the following snippet to create a singleton and initializate some values including the observation exclusively used by the singleton:
class Singleton: NSObject {
static private var sharedInstanceObserver : NSKeyValueObservation!
static let sharedInstance: Singleton = {
let sInstance = Singleton()
sharedInstanceObserver = sInstance.observe(\Singleton.aProperty, options: .new) { st, value in
print(st, value)
}
return sInstance
}()
#objc dynamic var aProperty = false
func updateDoesntWork() {
aProperty = !aProperty
}
func updateDoesWork() {
Singleton.sharedInstance.aProperty = !aProperty
}
}
Related
I'm rewriting parts of an app, and found this code:
fileprivate let defaults = UserDefaults.standard
func storeValue(_ value: AnyObject, forKey key:String) {
defaults.set(value, forKey: key)
defaults.synchronize()
NotificationCenter.default.post(name: Notification.Name(rawValue: "persistanceServiceValueChangedNotification"), object: key)
}
func getValueForKey(_ key:String, defaultValue:AnyObject? = nil) -> AnyObject? {
return defaults.object(forKey: key) as AnyObject? ?? defaultValue
}
When CMD-clicking the line defaults.synchronize() I see that synchronize is planned deprecated. This is written in the code:
/*!
-synchronize is deprecated and will be marked with the NS_DEPRECATED macro in a future release.
-synchronize blocks the calling thread until all in-progress set operations have completed. This is no longer necessary. Replacements for previous uses of -synchronize depend on what the intent of calling synchronize was. If you synchronized...
- ...before reading in order to fetch updated values: remove the synchronize call
- ...after writing in order to notify another program to read: the other program can use KVO to observe the default without needing to notify
- ...before exiting in a non-app (command line tool, agent, or daemon) process: call CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)
- ...for any other reason: remove the synchronize call
*/
As far as I can interpret, the usage in my case fits the second description: synchronizing after writing, in order to notify others.
It suggests using KVO to ovserve, but how? When I search for this, I find a bunch of slightly older Objective-C-examples. What is the best practice for observing UserDefaults?
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()
}
From the blog of David Smith
http://dscoder.com/defaults.html
https://twitter.com/catfish_man/status/674727133017587712
If one process sets a shared default, then notifies another process to
read it, then you may be in one of the very few remaining situations
that it's useful to call the -synchronize method in: -synchronize acts
as a "barrier", in that it provides a guarantee that once it has
returned, any other process that reads that default will see the new
value rather than the old value.
For applications running on iOS 9.3
and later / macOS Sierra and later, -synchronize is not needed (or
recommended) even in this situation, since Key-Value Observation of
defaults works between processes now, so the reading process can just
watch directly for the value to change. As a result of that,
applications running on those operating systems should generally never
call synchronize.
So in most likely case you do not need to set to call synchronize. It is automatically handled by KVO.
To do this you need add observer in your classes where you are handling persistanceServiceValueChangedNotification notification. Let say you are setting a key with name "myKey"
Add observer in your class may be viewDidLoad etc
UserDefaults.standard.addObserver(self, forKeyPath: "myKey", options: NSKeyValueObservingOptions.new, context: nil)
Handle the observer
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
//do your changes with for key
}
Also remove your observer in deinit
For anyone who will be looking for the answer in the future, didChangeNotification will be posted only if changes are made on the same process, if you would like to receive all updates regardless of the process use KVO.
Apple doc
This notification isn't posted when changes are made outside the current process, or when ubiquitous defaults change. You can use key-value observing to register observers for specific keys of interest in order to be notified of all updates, regardless of whether changes are made within or outside the current process.
Here is a link to demo Xcode project which shows how to setup block based KVO on UserDefaults.
Swift 4 version made with reusable types:
File: KeyValueObserver.swift - General purpose reusable KVO observer (for cases where pure Swift observables can't be used).
public final class KeyValueObserver<ValueType: Any>: NSObject, Observable {
public typealias ChangeCallback = (KeyValueObserverResult<ValueType>) -> Void
private var context = 0 // Value don't reaaly matter. Only address is important.
private var object: NSObject
private var keyPath: String
private var callback: ChangeCallback
public var isSuspended = false
public init(object: NSObject, keyPath: String, options: NSKeyValueObservingOptions = .new,
callback: #escaping ChangeCallback) {
self.object = object
self.keyPath = keyPath
self.callback = callback
super.init()
object.addObserver(self, forKeyPath: keyPath, options: options, context: &context)
}
deinit {
dispose()
}
public func dispose() {
object.removeObserver(self, forKeyPath: keyPath, context: &context)
}
public static func observeNew<T>(object: NSObject, keyPath: String,
callback: #escaping (T) -> Void) -> Observable {
let observer = KeyValueObserver<T>(object: object, keyPath: keyPath, options: .new) { result in
if let value = result.valueNew {
callback(value)
}
}
return observer
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if context == &self.context && keyPath == self.keyPath {
if !isSuspended, let change = change, let result = KeyValueObserverResult<ValueType>(change: change) {
callback(result)
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
File: KeyValueObserverResult.swift – Helper type to keep KVO observation data.
public struct KeyValueObserverResult<T: Any> {
public private(set) var change: [NSKeyValueChangeKey: Any]
public private(set) var kind: NSKeyValueChange
init?(change: [NSKeyValueChangeKey: Any]) {
self.change = change
guard
let changeKindNumberValue = change[.kindKey] as? NSNumber,
let changeKindEnumValue = NSKeyValueChange(rawValue: changeKindNumberValue.uintValue) else {
return nil
}
kind = changeKindEnumValue
}
// MARK: -
public var valueNew: T? {
return change[.newKey] as? T
}
public var valueOld: T? {
return change[.oldKey] as? T
}
var isPrior: Bool {
return (change[.notificationIsPriorKey] as? NSNumber)?.boolValue ?? false
}
var indexes: NSIndexSet? {
return change[.indexesKey] as? NSIndexSet
}
}
File: Observable.swift - Propocol to suspend/resume and dispose observer.
public protocol Observable {
var isSuspended: Bool { get set }
func dispose()
}
extension Array where Element == Observable {
public func suspend() {
forEach {
var observer = $0
observer.isSuspended = true
}
}
public func resume() {
forEach {
var observer = $0
observer.isSuspended = false
}
}
}
File: UserDefaults.swift - Convenience extension to user defaults.
extension UserDefaults {
public func observe<T: Any>(key: String, callback: #escaping (T) -> Void) -> Observable {
let result = KeyValueObserver<T>.observeNew(object: self, keyPath: key) {
callback($0)
}
return result
}
public func observeString(key: String, callback: #escaping (String) -> Void) -> Observable {
return observe(key: key, callback: callback)
}
}
Usage:
class MyClass {
private var observables: [Observable] = []
// IMPORTANT: DON'T use DOT `.` in key.
// DOT `.` used to define `KeyPath` and this is what we don't need here.
private let key = "app-some:test_key"
func setupHandlers() {
observables.append(UserDefaults.standard.observeString(key: key) {
print($0) // Will print `AAA` and then `BBB`.
})
}
func doSomething() {
UserDefaults.standard.set("AAA", forKey: key)
UserDefaults.standard.set("BBB", forKey: key)
}
}
Updating defaults from Command line:
# Running shell command below while sample code above is running will print `CCC`
defaults write com.my.bundleID app-some:test_key CCC
As of iOS 13, there is now a cooler way to do this, using Combine:
import Foundation
import Combine
extension UserDefaults {
/// Observe UserDefaults for changes at the supplied KeyPath.
///
/// Note: first, extend UserDefaults with an `#objc dynamic` variable
/// to create a KeyPath.
///
/// - Parameters:
/// - keyPath: the KeyPath to observe for changes.
/// - handler: closure to run when/if the value changes.
public func observe<T>(
_ keyPath: KeyPath<UserDefaults, T>,
handler: #escaping (T) -> Void)
{
let subscriber = Subscribers.Sink<T, Never> { _ in }
receiveValue: { newValue in
handler(newValue)
}
self.publisher(for: keyPath, options: [.initial, .new])
.subscribe(subscriber)
}
}
I'm develop an app which has an set of unique string. I have a function to add, remove, modify item in the NSMutableSet. I want to use KVO (key value observer) to observe whenever the set has change (add, remove, modify item).
Here's my code:
dynamic var barCodeSet = NSMutableSet()
in viewDidload I add observe:
override func viewDidLoad() {
super.viewDidLoad()
addObserver(self, forKeyPath: #keyPath(barCodeSet), options: [.old,.new,.initial], context: nil)
}
And this is my observe function:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(barCodeSet) {
print(barCodeSet.count)
for barcode in barCodeSet {
print(barcode)
}
}
}
I don't know why the KVO is not working. How can I modify the code so that we can get notify when set items change?
Assuming you do not need to use NSMutableSet, you could use a didSet clause in the variable declaration. try the following in a playground:
import UIKit
class myClass {
init() {
}
dynamic var barCodeSet: Set<String> = Set<String>() {
didSet {
print(barCodeSet.count)
for barcode in barCodeSet {
print(barcode)
}
}
}
}
let thisClass = myClass()
thisClass.barCodeSet = ["Apples", "Bananas", "Oranges"]
thisClass.barCodeSet.insert("Grapes")
Whenever you set the value of barCodeSet, its count and contents are printed to the console.
For debugging purposes, I would like to set watchers/observers on models but I didn't find hint so far.
Notice I'm rather new in iOS development (less than a month) so I might be missing something.
If you want to observe a whole class of objects, you can make a query, apply filters and then observe these Results for Notifications.
If you want to observe changes to a single object, you can retrieve it and then observe the properties you're interested in via Key-Value Observation (KVO).
Here is an example on KVO in Realm (Swift):
Just for demonstration on how KVO i Realm with persistent objects work.
class MyRealmClass: Object {
dynamic var id = NSUUID().UUIDString
dynamic var date: NSDate?
override static func primaryKey() -> String? {
return "id"
}
}
class ViewController: UIViewController {
var myObject: MyRealmClass?
private var myContext = 0
override func viewDidLoad() {
super.viewDidLoad()
myObject = MyRealmClass()
try! uiRealm.write({ () -> Void in
myObject?.date = NSDate()
uiRealm.add(myObject!, update: true)
print("New MyClass object initialized with date property set to \(myObject!.date!)")
})
myObject = uiRealm.objects(MyRealmClass).last
myObject!.addObserver(self, forKeyPath: "date", options: .New, context: &myContext)
//Sleep before updating the objects 'date' property.
NSThread.sleepForTimeInterval(5)
//Update the property (this will trigger the observeValueForKeyPath(_:object:change:context:) method)
try! uiRealm.write({ () -> Void in
myObject!.date = NSDate()
})
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &myContext {
print("Date property has changed, new value is \(change![NSKeyValueChangeNewKey]!)")
}
}
}
I’m using KVO for a property in a subclassed PFObject which is already registered during initialize.
Everything is fine if I use 1 object. On the second object I get the error The class KVO_vs_PFObject.MyModel must be registered with registerSubclass before using Parse. I need multiple objects to observe properties.
I tried to use property observer(didSet) as an alternative on swift but the compiler won't let me since I'm using a managed property.
Does anyone know what's going on with this code?
Below is my code:
import UIKit
import Parse
class MyModel : PFObject, PFSubclassing {
static func parseClassName() -> String {
return "MyModel"
}
override class func initialize() {
var onceToken : dispatch_once_t = 0;
dispatch_once(&onceToken) {
self.registerSubclass()
}
}
#NSManaged var property1 : String?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var myObject = MyModel()
myObject.addObserver(self, forKeyPath: "property1", options: .New, context: nil)
myObject.property1 = "Hello"
myObject.removeObserver(self, forKeyPath: "property1")
// If I comment these 4 lines. myObject is happy observing the property.
var anotherObject = MyModel()
anotherObject.addObserver(self, forKeyPath: "property1", options: .New, context: nil)
anotherObject.property1 = "World"
anotherObject.removeObserver(self, forKeyPath: "property1")
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
var n : AnyObject? = change["new"]
switch keyPath {
case "property1" :
println("observed MyModel.property1 with value \(n)")
default :
break
}
}
}
I ended up using computed property instead of stored property as per related post. Subclassing PFObject in Swift
// #NSManaged var property1 : String?
var property1: String? {
get {
return self["property1"] as? String
}
set {
self["property1"] = newValue
println("observed MyModel.property1 with value \(newValue)")
}
}
I have this in my AppDelegate in addition to the initialize(). There were some posts here that say that initialize() needs some kick-starting before getting invoked. Worth trying the following...
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launc
//-----------------Parse customizations------------------------------
// Enable storing and querying data from Local Datastore.
// Remove this line if you don't want to use Local Datastore features or want to use cachePolicy.
Parse.enableLocalDatastore()
// ****************************************************************************
// Uncomment this line if you want to enable Crash Reporting
// ParseCrashReporting.enable()
//
// Uncomment and fill in with your Parse credentials:
//This one is for the MyAppName Parse App
MyModel.registerSubclass()
// Rest of the stuff...
I'm assembling a class which has several states, as defined by an enum, and a read-only property "state" which returns the instance's current state. I was hoping to use KVO techniques to observe changes in state but this doesn't seem possible:
dynamic var state:ItemState // Generates compile-time error: Property cannot be marked dynamic because its type cannot be represented in Objective-C
I guess I could represent each state as an Int or String, etc. but is there a simple alternative workaround that would preserve the type safety that the enum would otherwise provide?
Vince.
Perhaps this is only available in swift 2+, but you can make an enum property directly observable without having to refer to its rawValue. It does come with some limitations however.
have the containing class extend from NSObject (directly or indirectly)
mark the enum with #objc
extend the enum from Int
declare the property as dynamic.
class SomeModel : NSObject { // (1) extend from NSObject
#objc // (2) mark enum with #objc
enum ItemState : Int, CustomStringConvertible { // (3) extend enum from Int
case Ready, Set, Go
// implementing CustomStringConvertible for example output
var description : String {
switch self {
case .Ready: return "Ready"
case .Set: return "Set"
case .Go: return "Go"
}
}
}
dynamic var state = ItemState.Ready // (4) declare property as dynamic
}
Elsewhere:
class EnumObserverExample : NSObject {
private let _model : SomeModel
init(model:SomeModel) {
_model = model
super.init()
_model.addObserver(self, forKeyPath:"state", options: NSKeyValueObservingOptions.Initial, context: nil)
}
deinit {
_model.removeObserver(self, forKeyPath:"state", context: nil)
}
override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if "state" == keyPath {
print("Observed state change to \(_model.state)")
}
}
}
let model = SomeModel()
let observer = EnumObserverExample(model:model)
model.state = .Set
model.state = .Go
Outputs:
Observed state change to Ready (because .Initial was specified)
Observed state change to Set
Observed state change to Go
I came across the same problem a while ago.
In the end I used an enum for the state and added an additional 'raw' property which is set by a property observer on the main state property.
You can KVO the 'raw' property but then reference the real enum property when it changes.
It's obviously a bit of a hack but for me it was better than ditching the enum altogether and losing all the benefits.
eg.
class Model : NSObject {
enum AnEnumType : String {
case STATE_A = "A"
case STATE_B = "B"
}
dynamic private(set) var enumTypeStateRaw : String?
var enumTypeState : AnEnumType? {
didSet {
enumTypeStateRaw = enumTypeState?.rawValue
}
}
}
ADDITIONAL:
If you are writing the classes that are doing the observing in Swift here's a handy utility class to take some of the pain away.
The benefits are:
no need for your observer to subclass NSObject.
observation callback code as a closure rather than having to implement
observeValueForKeyPath:BlahBlah...
no need to make sure you removeObserver, it's taken care of for you.
The utility class is called KVOObserver and an example usage is:
class ExampleObserver {
let model : Model
private var modelStateKvoObserver : KVOObserver?
init(model : Model) {
self.model = model
modelStateKvoObserver = KVOObserver.observe(model, keyPath: "enumTypeStateRaw") { [unowned self] in
println("new state = \(self.model.enumTypeState)")
}
}
}
Note [unowned self] in the capture list to avoid reference cycle.
Here's KVOObserver...
class KVOObserver: NSObject {
private let callback: ()->Void
private let observee: NSObject
private let keyPath: String
private init(observee: NSObject, keyPath : String, callback: ()->Void) {
self.callback = callback
self.observee = observee
self.keyPath = keyPath;
}
deinit {
println("KVOObserver deinit")
observee.removeObserver(self, forKeyPath: keyPath)
}
override func observeValueForKeyPath(keyPath: String,
ofObject object: AnyObject,
change: [NSObject : AnyObject],
context: UnsafeMutablePointer<()>) {
println("KVOObserver: observeValueForKey: \(keyPath), \(object)")
self.callback()
}
class func observe(object: NSObject, keyPath : String, callback: ()->Void) -> KVOObserver {
let kvoObserver = KVOObserver(observee: object, keyPath: keyPath, callback: callback)
object.addObserver(kvoObserver, forKeyPath: keyPath, options: NSKeyValueObservingOptions.New | NSKeyValueObservingOptions.Initial, context: nil)
return kvoObserver
}
}