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.
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)
}
}
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
}
}
I am trying to figure out how to evaluate the [NSKeyValueChangeKey : AnyObject] change dictionary parameter in func observeValue(forKeyPath.... I have the following code in a playground and the way I'm evaluating the change dictionary I always end up thinking the change is a NSKeyValueChange.setting (which is definitely wrong).
What is the right way to evaluate the change dictionary?
import Foundation
class KVOTester: NSObject {
dynamic var items = [Int]() // Observe via KVO
override init() {
super.init()
self.addObserver(self, forKeyPath: #keyPath(KVOTester.items), options: [], context: nil)
}
deinit {
self.removeObserver(self, forKeyPath: #keyPath(KVOTester.items))
}
func exerciseKVO() {
self.items = [Int]() // NSKeyValueChange.setting
self.items.append(1) // NSKeyValueChange.insertion
self.items[0] = 2 // NSKeyValueChange.replacement
self.items.remove(at: 0) // NSKeyValueChange.removal
}
override func observeValue(forKeyPath keyPath: String?, of object: AnyObject?, change: [NSKeyValueChangeKey : AnyObject]?, context: UnsafeMutablePointer<Void>?) {
// We are only interested in changes to our items array
guard keyPath == "items" else { return }
// #1: object is the KVOTester instance - why isn't it the items array?
// #2 I don't understand how to use the change dictionary to determine what type of change occurred. The following
// is wrong - it *always* prints "Setting".
if let changeKindValue = change?[.kindKey] as? UInt, changeType = NSKeyValueChange(rawValue: changeKindValue) {
switch changeType {
case .setting:
print("Setting")
break
case .insertion:
print("Insertion")
break
case .removal:
print("Removal")
break
case .replacement:
print("Replacement")
break
}
}
}
}
let kvoTester = KVOTester()
kvoTester.exerciseKVO()
As the original questioner pointed out in his comment, the following code will give the expected result:
import Foundation
class KVOTester: NSObject {
dynamic var items = [Int]() // Observe via KVO
override init() {
super.init()
self.addObserver(self, forKeyPath: #keyPath(KVOTester.items), options: [], context: nil)
}
deinit {
self.removeObserver(self, forKeyPath: #keyPath(KVOTester.items))
}
func exerciseKVO() {
let kvoArray = self.mutableArrayValue(forKey: #keyPath(KVOTester.items))
items = [Int]() // NSKeyValueChange.setting
kvoArray.add(1) // NSKeyValueChange.insertion
kvoArray.replaceObject(at: 0, with: 2) // NSKeyValueChange.replacement
kvoArray.removeObject(at: 0) // NSKeyValueChange.removal
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// We are only interested in changes to our items array
guard keyPath == "items" else { return }
if let changeKindValue = change?[.kindKey] as? UInt,
let changeType = NSKeyValueChange(rawValue: changeKindValue) {
switch changeType {
case .setting:
print("Setting")
break
case .insertion:
print("Insertion")
break
case .removal:
print("Removal")
break
case .replacement:
print("Replacement")
break
}
}
}
}
let kvoTester = KVOTester()
kvoTester.exerciseKVO()
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...