"hidden" vs #keyPath(UIView.isHidden) - ios

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.

Related

iOS KVO - detect when the same value is set again

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"];

was sent to an object that is not KVC-compliant for the "com" property

I want to observe the value stored in UserDefaults.standard under the key: com.apple.configuration.managed, so I did this:
UserDefaults.standard.addObserver(self, forKeyPath: "com.apple.configuration.managed", options: [.new, .old], context: nil)
I then implemented this:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
}
When I run the app observeValue... is never called when that default is changed, instead Xcode crashes with this error:
*** Terminating app due to uncaught exception
'NSUnknownKeyException', reason: '[
addObserver:
forKeyPath:#"com.apple.configuration.managed" options:1 context:0x0]
was sent to an object that is not KVC-compliant for the "com"
property.'
What's the right way of observing com.apple.configuration.managed in the UserDefaults.standard?
You can use KVC observing to observe properties of an object, and you can use KVC observing to observe the values that you assign to UserDefaults, but you cannot use KVC to observe a value where the value identifier contains periods, since this is used to describe a hierarchical keypath.
So, the exception message is correct, UserDefaults is not KVC compliant for the com property; what you have asked is to be notified when the managed property of the object referred to by the configuration property of the object referred to by the apple property of the object referred to by the com property of the UserDefaults object is modified.
So you can use KVC to be notified when "MyDefault" changes but not when "My.Default" changes. If you can't change the name of your user default then you will need to observe the NSUserDefaultsDidChangeNotification Notification

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

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