Swift KVO - Observing enum properties - swift

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

Related

Observing non-property object for changes [duplicate]

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

Key-value observation weird behavior

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

How to set watchers on Realm model?

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]!)")
}
}
}

Using switch in observeValueForKeyPath

I'm attempting to increase legibility of my KVO observeValueForKeyPath implementation by replacing the typical long string of nested if/else statements with a single switch statement.
So far, the only thing that's actually worked is:
private let application = UIApplication.sharedApplication()
switch (object!, keyPath!) {
case let (object, "delegate") where object as? UIApplication === application:
appDelegate = application.delegate
break
...
default:
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
Which, if anything, is even harder to read than:
if object as? UIApplication === application && keyPath! == "delegate" {
}
else {
}
Does anybody have a good model for using switch in observeValueForKeyPath (and similar methods)
EDIT: Relevant to #critik's question below, here's more of the code to demonstrate the problems with just using switch (object as! NSObject, keyPath!) {:
private let application = UIApplication.sharedApplication()
private var appDelegate : UIApplicationDelegate?
private var rootWindow : UIWindow?
public override func observeValueForKeyPath(
keyPath: String?,
ofObject object: AnyObject?,
change: [String : AnyObject]?,
context: UnsafeMutablePointer<Void>) {
switch (object as! NSObject, keyPath!) {
case (application, "delegate"):
appDelegate = application.delegate
(appDelegate as? NSObject)?.addObserver(self, forKeyPath: "window", options: [.Initial], context: nil)
break
case (appDelegate, "window"):
rootWindow = appDelegate?.window?.flatMap { $0 }
break
case (rootWindow, "rootViewController"):
rebuildViewControllerList(rootWindow?.rootViewController)
break
default:
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
How about a switch on tuples:
switch (object as! NSObject, keyPath!) {
case (application, "delegate"):
appDelegate = application.delegate
...
default:
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
Note 1. Although I'm agains forced stuff in Swift (downcasts, unwraps, etc), you can safely force downcast to NSObject without getting a crash, as per this SO question, KVO is only available for NSObject subclasses.
Note 2. You also don't need a break in Swift, this will also shorten your code by at least one line :)
This doesn't really solve the problem of using switch in observeValueForKey but it does demonstrate how I simplified the problem space to eliminate the verbose code.
I created a utility class, KVOValueWatcher which allows me to add (and remove) KVO observation on a single property of an object:
public class KVOValueWatcher<ObjectType:NSObject, ValueType:NSObject> : NSObject {
public typealias OnValueChanged = (ValueType?, [String:AnyObject]?) -> ()
let object : ObjectType
let keyPath : String
let options : NSKeyValueObservingOptions
let onValueChanged : OnValueChanged
var engaged = false
public init(object:ObjectType, keyPath:String, options : NSKeyValueObservingOptions = [], onValueChanged: OnValueChanged) {
self.object = object
self.keyPath = keyPath
self.onValueChanged = onValueChanged
self.options = options
super.init()
engage()
}
deinit {
if(engaged) {
print("KVOValueWatcher deleted without being disengaged")
print(" object: \(object)")
print(" keyPath: \(keyPath)")
}
disengage()
}
public func engage() {
if !engaged {
self.object.addObserver(self, forKeyPath: keyPath, options: options, context: nil)
engaged = true
}
}
public func disengage() {
if engaged {
self.object.removeObserver(self, forKeyPath: keyPath)
engaged = false
}
}
override public func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
self.onValueChanged(((object as? NSObject)?.valueForKeyPath(keyPath!) as? ValueType), change)
}
}
My problem code then becomes:
rootWindowWatcher = KVOValueWatcher(object: applicationDelegate as! NSObject, keyPath: "window", options: [.Initial]) { window, changes in
self.rootViewWatcher?.disengage()
self.rootViewWatcher = nil
if let window = window {
self.rootViewWatcher = KVOValueWatcher(object: window, keyPath: "rootViewController", options: [.Initial]) {
[unowned self] rootViewController, changes in
self.rootViewController = rootViewController
self.rebuildActiveChildWatchers()
}
}
}
The primary reason for the change was because it was becoming a nightmare to maintain all the different observations and get them properly added and removed. Adding the wrapper class eliminates the problem and groups watching a property and the action to be taken when the property changes in the same location.

UnsafeMutablePointer<Void> to Concrete Object Type

How can I cast from UnsafeMutablePointer<Void> to Concrete Object Type
I've created a KVO observer and passed a custom class as context just like this
class Info : NSObject
{
}
class Foo : NSObject {
dynamic var property = ""
}
var info = Info()
class Observing : NSObject {
func observe1(object: NSObject) {
object.addObserver(self, forKeyPath: "property", options: .New, context: &info)
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
println("observed \(keyPath) change:'\(change)' context: \(context)")
}
}
let o = Observing()
var object = Foo()
o.observe1(object)
object.property = "new value"
I would like to know how to cast context back to Info class
UnsafeMutablePointer has an initializer that takes another UnsafeMutablePointer of another type, the result being the same pointer but to the new type.
So in your case:
let infoPtr = UnsafeMutablePointer<Info>(context)
let info = infoPtr.memory
Beware though this is, as the docs describe it, "a fundamentally unsafe conversion". If the pointer you have is not a pointer to the type you convert it to, or if the memory you are now accessing it is not valid in this context, you may end up accessing invalid memory and your program will get a little crashy.