iOS KVO - detect when the same value is set again - ios

Is it possible to use KVO in a way that it detects not only if the value changed, but also if the same value was set again? I'm currently receiving notifications only when the value changed (is different from the previously set one). I need to receive notification every time the value is set (even if it's the same as the one previously set). How can I achieve this?
My code:
private func addObserver() {
defaults.addObserver(self, forKeyPath: DefaultsKeys.testKey._key, options: .new, context: nil)
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let value = change?[NSKeyValueChangeKey.newKey] as? Bool else { return }
statusCallback?(value)
}
private func removeObserver() {
defaults.removeObserver(self, forKeyPath: DefaultsKeys.testKey._key)
}

KVO generally is called every time the observed property is set, even if it's the same value it was last time. But I guess you're observing UserDefaults, and which has an idiosyncrasy that prevents this from happening (probably an optimization that prevents unnecessary saves of the store).
You can register for .didChangeNotification, which appears to called whether the value changed or not:
NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, object: nil, queue: .main) { notification in
print("notification", notification)
}

You can achieve this by doing like:
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
let value = change?[.oldKey] as? Bool
guard value == nil || value != yourVariableToCheck else { return }
statusCallback?(value)
}
Only change yourVariableToCheck on your own variable.

KVO mechanism is very simple - it does not perform any additional checks upon setting a new value, it is merely triggered when a setter is called. Hence it is not possible to differentiate if the value is different from that which is already being set. And it's good. Firstly, because, it's not typical in practice to assign the same value to a variable.
Second, introducing additional checks would be consumable and in most cases unneeded. If that check existed, it would negatively affect performance.
That being said, as far as Swift is concerned, you can consider replacing KVO-mechanism (essentially an Objective-C legacy) with native Swift property observers: willSet and didSet. And this would essentially play the same role as by passing both options: NSKeyValueObservingOptionNew and NSKeyValueObservingOptionOld (.old and .new in Swift) to addObserver method. Once having specified these flags, whenever KVO mechanism is triggered, you will receive both values (old an new) in observeValue(...), from where you can decide what to do with either. But why would you need such complexity when willSet does practically the same and is much more conveinent:
var myVariable : String! {
willSet {
print("Old value is: \(myVariable)")
print("New value is: \(newValue)")
// Let's do something with our old value
}
didSet {
print("Just set the new value: \(newValue)")
// New value is set. Let's do some actions.
}
}

If you want to track every setting a value in NSUserDefaults, even if the new value is the same as previous, wrap the value with the NSDictionary, and put inside the dictionary an NSUUID value, generated every time as a new setValue being called.
Before (observeValueForKeyPath did not called on every setValue):
[self.mySharedDefaults setValue: #"CHECKING" forKey:#"appStatusOUT"];
After (observeValueForKeyPath being called on every setValue):
[self.mySharedDefaults setValue: [NSDictionary dictionaryWithObjectsAndKeys: #"CHECKING", #"CMD",
[NSUUID UUID].UUIDString, #"UUID", nil] forKey:#"appStatusOUT"];

Related

"hidden" vs #keyPath(UIView.isHidden)

KVO observer with #keyPath(UIView.isHidden) does not work, but "hidden" works.
Very strange. Is it bug or feature?
child.addObserver(self, forKeyPath: "hidden", options: [.initial,.new], context: nil);
override func observeValue(forKeyPath keyPath: String?, of object: Any?, .change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let view = object as? UIView, view.superview === self && keyPath == "hidden" {
print("*");
}
}
Is it bug or feature?
Let's say it's a known fact. "Renamification" means that Swift pretends that the name of an Objective-C Bool property starts with is... even when it doesn't. But the #keyPath mechanism didn't get the memo when it comes to KVO and property setter names, and property setter swizzling to implement KVO observing is purely an Objective-C feature, so you have to use the real name of the property / setter, i.e. the Objective-C name, so that communication with Objective-C works correctly for KVO observation purposes.
I've filed a bug report on it (https://bugs.swift.org/browse/SR-2415) on the grounds that Swift could behave a little smarter about this, but until the Swift gang respond, it's just something you have know and deal with.

Using KVO to observing view.center in swift for ios give back NSPoint?

Here is the code
view.addObserver(self, forKeyPath: "center", options: .New, context: nil)
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
guard let change = change else {
return
}
print(change)
guard let newCenter = change["new"] else {
return
}
print(newCenter.dynamicType)
}
And the output is :
["new": NSPoint: {50, 50.5}, "kind": 1]
NSConcreteValue
I don't know why NS class will appear in iOS. How to correctly observe view.center/frame/transform using KVO with swift?
Since KVO is part of the Objective-C runtime, the change dictionary is an NSDictionary, not a native Swift dictionary. An NSDictionary can only hold Objective-C objects (which is why, in Swift, it becomes a [String:AnyObject], not a [String:Any]), and CGPoint is not an Objective-C object. So KVO has to wrap the CGPoint in an object.
NSValue is a generic Objective-C class for wrapping non-objects, and KVO uses it when the observed property's type is not an object type. NSValue is a “class cluster”, which means it defines an interface and may have specialized, non-public subclasses that implement the interface. In this case, you're seeing the name of one of those subclasses: NSConcreteValue.
You can get the CGPoint value from the NSValue by asking for its CGPointValue property:
guard let newCenter = change["new"]?.CGPointValue else {
return
}
The reason you see NSPoint when you print the change dictionary is an accident of history. The NSPoint type is older than CGPoint, but is now an alias for CGPoint on OS X (defined in Foundation/NSGeometry.h), and doesn't exist at all on iOS. However, the code to print NSPoint/CGPoint was not changed to use the new name (probably for backward compatibility) and the same code is used on both OS X and iOS.

[SearchStockCell retain]: message sent to deallocated instance

I'm getting the following error:
SearchStockCell retain]: message sent to deallocated instance 0x7f9fa1922c00
but I am having a hard time tracing the issue because whenever I profile with zombies, it stops without any warning or error(2-3 secs).
I'm using realm for this project and the data parsing is performed at background.Not sure if this information is relevant.
Is there other way to track this? or is possible I use weak for tableview cell?
Updated
class SearchStockCell: SSBaseTableCell {
#IBOutlet var symbolLabel: UILabel!
#IBOutlet var marketLabel: UILabel!
#IBOutlet var priceLabel: UILabel!
var stock: StockInfo? {
willSet{ "About to step to \(newValue)"
if let aStock = newValue {
// add KVO on newValue
aStock.addObserver(self,
forKeyPath: "price",
options: NSKeyValueObservingOptions.New,
context: nil)
aStock.addObserver(self,
forKeyPath: "change",
options: NSKeyValueObservingOptions.New,
context: nil)
}
}
didSet { "Just stepped from \(oldValue)"
if let aStock = oldValue {
// remove KVO on old value
aStock.removeObserver(self, forKeyPath: "price")
}
if let aStock = oldValue {
// remove KVO on old value
aStock.removeObserver(self, forKeyPath: "change")
}
self.configureCell()
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "price" {
self.updatePrice()
}
if keyPath == "change" {
self.updateChange()
}
}
....
...
..
.
Here is the code happened in SearchStockCell.
I will fire an API to update my visible cells(it will update my realm) which later will prompt the changes on the SearchStockCell by KVO. Note that I can't really reload the table again because I need to maintain the position of tableview and there's thousands rows of data in it.
It is really hard to guess from code but would try my best to look answer
Please look for places
have used SearchStockCell as a property somewhere if yes check for attributes strong/weak. There is possible cycle of holding each other.
Check if you are using SearchStockCell object in block, if yes try using it as weak object. Also check for other things done inside the block.
you are using KVO, check if at any point of time is observer going out of memory.
Most likely issue which I can think of right is at some place you are assigning/using SearchStockCell object as weak/strong due to which ARC is handling retain count wrongly.
It looks like you're vastly overcomplicating this situation by adding and balancing KVO on these table cells.
You mentioned that you don't want to reload the table since you'll lose your position in the scroll view. Have you considered simply saving the scroll position of the table view before reloading and then re-setting it afterwards?
As a side note, Realm will soon introduce a feature to track insertions/updates/deletions on a table view data source, so hopefully once that's out, you could use that here instead (Disclaimer: I work for Realm).

How can you collect data from NSOperations into an array?

I have a number of NSOperations which create some data asynchronously. I want to collect all of the results into one array. Because I'm accessing the array on multiple different threads, I've put locking around the array.
The NSOperationQueue is appending the data to the array but the results seem to miss some of the data objects. The results seem to change each time I run it.
I've created a simplified example project that recreates the issue. The code is in Swift but I don't think this is Swift-specific.
import UIKit
class ViewController: UIViewController {
let queue = NSOperationQueue()
var bucket = [String]()
override func viewDidLoad() {
super.viewDidLoad()
queue.addObserver(self, forKeyPath: "operations", options: NSKeyValueObservingOptions.New, context: nil)
for _ in 0..<10 {
queue.addOperation(NSBlockOperation {
// Let's pretend that creating the "fish" string is actually potentially
// expensive and that's why we're doing it in an NSOperation.
let fish = "fish"
objc_sync_enter(self.bucket)
self.bucket.append(fish)
let fishCount = self.bucket.count
print("Bucket contains \(fishCount) fish" + ((fishCount != 1) ? "es" : ""))
objc_sync_exit(self.bucket)
})
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if let keyPath = keyPath {
if let object = object as? NSOperationQueue {
if object == queue && keyPath == "operations" {
if queue.operationCount == 0 {
objc_sync_enter(self.bucket)
let fishCount = bucket.count
print("Bucket finally contains \(fishCount) fish" + ((fishCount != 1) ? "es" : ""))
objc_sync_exit(self.bucket)
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
}
}
}
The results vary but are often something like this:
Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 2 fishes
Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 3 fishes
Also, sometimes the code crashed with an EXC_BAD_ACCESS on the line self.bucket.append(fish)
In addition, the line print("Bucket finally contains \(fishCount) fish" + ((fishCount != 1) ? "es" : "")) in observeValueForKeyPath never gets called. I'm not sure if this is a separate issue or not.
You should look at subclassing NSOperation, since it is an abstract class.
See this Stackoverflow question for subclassing.
With that in mind I would suggest that you have an identifier property on each operation instance so that you can keep track of your operations, that way you can tell when all of your operations have finished. You might also consider pulling this code out of your view controller class and creating a class to handle your fish Plus it will help you with encapsulation further down the road when say you are no longer interested in fish but cats :)
The Concurrency Programming Guide is really good at explaining the basics of asynchronous application design.
The NSOperation class is an abstract class you use to encapsulate the code and data associated with a single task. Because it is abstract, you do not use this class directly but instead subclass or use one of the system-defined subclasses (NSInvocationOperation or NSBlockOperation) to perform the actual task. Despite being abstract, the base implementation of NSOperation does include significant logic to coordinate the safe execution of your task. The presence of this built-in logic allows you to focus on the actual implementation of your task, rather than on the glue code needed to ensure it works correctly with other system objects.

Swift: Custom Setter For CoreData NSManagedObject

How to implement custom setter for NSManagedObject in Swift. I need to do task before setting the NSMangedObject Property.
My recommendation would be to use KVC. Maybe not the most elegant solution, but conceptionally a logical application of KVC.
Observe a change of the attribute. Register for the change in init(entity:insertIntoManagedObjectContext:) or maybe better in awakeFromFetch and awakeFromInsert, and remove the observer in willTurnIntoFault.
init(entity: NSEntityDescription!, insertIntoManagedObjectContext context: NSManagedObjectContext!) {
super.init(entity: entity, insertIntoManagedObjectContext: context)
addObserver(self, forKeyPath: "attribute", options: NSKeyValueObservingOptions.New | NSKeyValueObservingOptions.Old, context: nil)
}
override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: NSDictionary!, context: CMutableVoidPointer) {
if (keyPath == "attribute") {
// do what you need to do
}
}
Updated for Swift 3:
init(entity: NSEntityDescription!, insertIntoManagedObjectContext context: NSManagedObjectContext!) {
super.init(entity: entity, insertIntoManagedObjectContext: context)
addObserver(self, forKeyPath: "attribute", options: [.old, .new], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "attribute" {
// do what you need to do
}
}
There is even a simpler way how to do it without managing KVO subscription. It can be done simply by overriding didChangeValueForKey: like this:
override func didChangeValueForKey(key: String) {
super.didChangeValueForKey(key)
if key == "propertyName" {
// do something now when propertyName changed
}
}
TL;DR
I would recommend overriding awakeFromInsert instead of the init because it doesn't require KVO and the object properties can be accessed safely. Overriding any of the init methods is risky and unnecessary since the object + its properties may not be ready to be accessed (faults).
Explanation
Overriding any of the init methods is risky and unnecessary since the object + its properties may not be ready to be accessed (faults). However, it’s very useful to be able to prepare an NSManagedObject before it starts accepting data. Perhaps we want to set up some logical defaults or assign some relationships before handing the object to the user. In these situations, we use awakeFromInsert. As the name implies, this method is called right after the NSManagedObject is created from an insert call.
This method is called before any values are set and is a perfect opportunity to set default values, initialize transient properties, and perform other tasks that we would normally handle in the init method. This method is called exactly once in the entire lifetime of an object. It won’t be called on the next execution of the application, and it won’t be called when an object is read in from the persistent store. Therefore, we don’t need to worry about overriding values that have been set previously. When we override this method, we should be sure to call super.awakeFromInsert() at the very beginning of our implementation to allow the NSManagedObject to finish anything it needs to before we begin our code.
Handy ObjC Shortcut in Xcode Snippets (Xcode 12)
NOTE: It's very easy to add your own Swift snippet for this manually. You can also try some GH repo. e.g for Swift 4 snippets.

Resources