When to unregister KVO observation of Operation isFinished - swift

In this simple code (Xcode 8.3), I create an Operation subclass instance, register for KVO observation of its isFinished property, and launch the operation by adding it to my queue:
class MyOperation : Operation {
override func main() {
print("starting")
print("finishing")
}
}
class ViewController: UIViewController {
let q = OperationQueue()
override func viewDidLoad() {
super.viewDidLoad()
let op = MyOperation()
op.addObserver(self, forKeyPath: #keyPath(MyOperation.isFinished), options: [], context: nil)
self.q.addOperation(op)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("Observed \(keyPath)")
if let op = object as? Operation {
op.removeObserver(self, forKeyPath: #keyPath(MyOperation.isFinished))
}
}
}
As you can see, I have an implementation of observeValue(forKeyPath..., of course, and my plan was to call removeObserver(forKeyPath... there.
The problem is that my app crashes with "MyOperation was deallocated while key value observers were still registered with it". We print "starting" and "finishing" but we never print "Observed"; the operation goes out of existence before I get my KVO notification.
This seems like a catch-22. If I can't remove the observer by observing isFinished, when am I supposed to do it? [I can work around this issue by adding to MyOperation my own KVO-observable property that I set at the end of main. But the notion that I should have to do this is very odd; isn't this exactly why isFinished is observable, so that I can do what I'm trying to here?]

After testing the exact same given code snippet on Xcode 8.2, it worked as it should, the console shows:
starting
finishing
Observed Optional("isFinished")
It seems that the reason of the issue is testing it on Xcode 8.3, probably it is a bug -or it might be a new behavior-. However, I would suggest to report it as a bug.

Apple changed #keyPath behavior in Swift 3.1 (source). Currently #keyPath(isFinished) returns "finished", it used to return "isFinished", and that was a bug. Here is the explanation as it can easily get confusing when using KVO and Operation class.
When you register an object for KVO notifications
textView.addObserver(self,
forKeyPath: #keyPath(UITextView.isEditable),
options: [.new, .old],
context: nil)
Foundation provides it (textView) with new setter implementation that calls willChangeValue(forKey:) and didChangeValue(forKey:) (this is done via isa-swizzling). The key that is passed to those methods is not getter (isEditable) not setter(setEditable:) but property name (editable).
#property(nonatomic,getter=isEditable) BOOL editable
This is why it is expected to receive editable from #keyPath(UITextView.isEditable).
Although Operation class has properties defined ad follows
#property (readonly, getter=isExecuting) BOOL executing;
#property (readonly, getter=isFinished) BOOL finished;
It expects to observe notifications for isFinished and isExecuting keys.
Upon completion or cancellation of its task, your concurrent operation object must generate KVO notifications for both the isExecuting and isFinished key paths to mark the final change of state for your operation
This forces us to use literal strings when posting those notifications.
IMHO this is a mistake made years ago which is really hard to recover from without breaking existing code.

Related

MacOS uses KVO to perform multiple executions [duplicate]

This question already has answers here:
KVO broken in iOS 9.3
(3 answers)
Closed 5 years ago.
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("in viewDidLoad");
// addObserver keyPath
UserDefaults.standard.addObserver(self, forKeyPath: "testKey", options: .new, context: nil);
print("out viewDidLoad");
}
deinit {
// removeObserver keyPath
UserDefaults.standard.removeObserver(self, forKeyPath: "testKey");
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("in observeValue keyPath: \(keyPath) value: \(UserDefaults.standard.integer(forKey: "testKey"))");
// 1. If I execute the func click () method, it will be executed two times
// 2. If App originally existed "testKey", then func observeValue () will be executed after the viewDidLoad is finished.
}
#IBAction func click(_ sender: NSButton) {
UserDefaults.standard.set(arc4random(), forKey: "testKey");
}
}
The above code is all of my test code. I used KVO in my own project, but found repeated execution.
// 1. If I execute the func click () method, it will be executed two times
// 2. If App originally existed "testKey", then func observeValue () will be executed after the viewDidLoad is finished.
This is not what I understand about KVO. My idea is that after addObserver, my observeValue will be called if my key is changed. But it didn't turn out that way. I tried to find the answer to the forum, and I didn't find the answer. I just found a similar question.
If I press Button in my view, then the final result will be..:
in viewDidLoad
out viewDidLoad
in observeValue keyPath: Optional("testKey") value: 4112410111
in observeValue keyPath: Optional("testKey") value: 3712484288
in observeValue keyPath: Optional("testKey") value: 3712484288
macos: 10.12.6 (16G29)
xcode: 9 beta6、xcode 8.3.3
If you have the same problem, please tell more people to help us solve it. Thank you
I have sent the same question to the official, and if there is a solution, I will return it here.
From setting a breakpoint in observeValue() and looking at the trace, it appears that the observations are getting fired in two places; one during click() as an effect of the line where you tell UserDefaults to set the value, and another later on, scheduled on the run loop so it happens after click() has already returned, when the system detects that the value has changed. This double notification could probably be considered a bug, since the latter notification should render the former unnecessary, and I'd consider filing a radar report on it.
Unfortunately, I can't see any way to disable this behavior. I can think of a workaround, but it's extremely hacky, kludgey, ugly, and I probably wouldn't actually do it unless the need is absolutely dire. But here it is:
private var kvoContext = 0
private let ignoreKVOKey = "com.charlessoft.example.IgnoreKVO"
// If this can be called from a thread other than the main thread,
// then you will need to take measures to protect it against race conditions
private var shouldIgnoreKVO = false
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &self.kvoContext { // always, always use a context pointer
if !shouldIgnoreKVO { // if this is a notification we don't want, ignore it
print("in observeValue keyPath: \(String(describing: keyPath)) value: \(UserDefaults.standard.integer(forKey: "testKey"))");
}
} else {
// call super if context pointer doesn't match ours
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
#IBAction func click(_ sender: NSButton) {
// we don't need this notification, since we'll get the later one
// resulting from the defaults having changed
self.shouldIgnoreKVO = true
defer { self.shouldIgnoreKVO = false }
UserDefaults.standard.set(arc4random(), forKey: "testKey");
}
Again, it's ugly, it's hacky, I probably wouldn't actually do it. But there it is.

Crash when trying to remove KVO observer

I have implemented KVO for the first time and in certain conditions, it is working and the observeValue is getting called correctly. However, I'm getting a crash when trying to remove the observer in deinit:
Cannot remove an observer
for
the key path "downloadInProgress" from
because it is not registered as an observer.
...although I did register the object in viewDidLoad.
// At the top of my file
dynamic var downloadInProgress: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
self.addObserver(self, forKeyPath: #keyPath(downloadInProgress), options: [.old,.new], context: nil)
}
deinit {
// It crashes here
removeObserver(self, forKeyPath: #keyPath(downloadInProgress))
}
Basically if downloadInProgress = false, it crashes. What am I doing wrong? Thanks.
You wrote
// At the top of my file
dynamic var downloadInProgress: Bool = false
So it isn't part of an object? If so that might be the problem. KeyValueObserving is a technic from ObjectiveC. In Swift there are some limitations for it. One limitation is that it only works at classes that derive from NSObject. If it's a global var as I expect this isn't fulfilled.
Apple documentation:
You can use key-value observing with a Swift class, as long as the class inherits from the NSObject class
It should work if you have an object with kvo like that:
final class MyObject: NSObject {
dynamic var downloadInProgress: Bool = false
}
self.addObserver(self, forKeyPath: #keyPath(myobjectinstance.downloadInProgress), options: [.old,.new], context: nil)
Hint: Try to avoid KVO in future, because it's uncommon in swift.

What's causing this? - "CoreAnimation: warning, deleted thread with uncommitted CATransaction"

Xcode 8.2.1 / macOS 10.12.4 beta
I'm trying to understand an error I'm getting:
CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces, or set CA_ASSERT_MAIN_THREAD_TRANSACTIONS=1 to abort when an implicit transaction isn't created on a main thread.
I have a block of code in a swift file that sends a network request, retrieves some data and processes it before posting a notification:
// global variable
var data: [String: Any] = [:]
func requestData() {
...
do {
// process data
data = processedData
NotificationCenter.default.post(name: didProcessData, object: nil)
}
} catch {
...
}
In my main ViewController.swift file, an observer listens for the notification and updates the view.
func updateView() {
// update the view
textField.stringValue = "..."
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(updateView), name: didProcessData, object: nil)
}
When updateView() is called, the actual text fields and such take a random amount of time to actually update, and the aforementioned error appears.
My guess was that there was an issue with thread safety, so I changed updateView() like so:
func updateView() {
DispatchQueue.main.async(execute: {
// update the view
self.textField.stringValue = "..."
})
}
And now the view updates properly. But I'm still relatively inexperienced with programming, and I don't quite understand what exactly is causing the error.
Your notification handler (updateView) is called from an arbitrary thread -- e.g. not from the main thread (so-called UI-Thread). Several framework APIs detect when they are called from outside the main thread (as a kind-of warning).
If you want to update the UI, you'll have to make sure that this code is executed in the main thread; this is typically accomplished by enqueuing a closure (work item) to DispatchQueue.main.async, which is part of GCD (Grand Central Dispatch).

Is removing a NotificationCenter observer that was created with closure syntax by name adequate?

I have a few notifications that were created using block / trailing closure syntax which look like this:
NotificationCenter.default.addObserver(forName: .NSManagedObjectContextObjectsDidChange, object: moc, queue: nil) { note in
// implementation
}
Which I was later removing by name, like this:
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: moc)
My Question
Is this adequate? Or do I absolutely need to save the NSObjectProtocol to it's own property and remove that property with the following syntax?
NotificationCenter.default.removeObserver(didChangeNotification)
You absolutely need to store the return value in a property and remove that later on.
From https://developer.apple.com/reference/foundation/nsnotificationcenter/1411723-addobserverforname:
Return Value
An opaque object to act as the observer.
When you call any one of the removeObserver methods, the first parameter is the observer to remove. When you set up a block to respond to a notification, self is not the observer, NSNotificationCenter creates its own observer object behind the scenes and returns it to you.
Note: as of iOS 9, you are no longer required to call removeObserver from dealloc/deinit, as that will happen automatically when the observer goes away. So, if you're only targeting iOS 9, this may all just work, but if you're not retaining the returned observer at all, the notification could be removed before you expect it to be. Better safe than sorry.
To add to #Dave's answer, it looks like documentation isn't always 100% accurate. According to this article by Ole Begemann there is a contradiction in the doc and self-removing magic was not happening as of iOS 11.2 in his test app.
So that the answer is still "Yes, one needs to remove that observer manually" (and yes, self is not the observer, the result of addObserver() method is the observer).
Here an example with code, for how a correct implementation looks like:
Declare the variable that gets returned when you add the observer in your class A (the receiver of the notification or observer):
private var fetchTripsNotification: NSObjectProtocol?
In your init method add yourself as an observer:
init() {
fetchTripsNotification = NotificationCenter.default.addObserver(forName: .needsToFetchTrips, object: nil, queue: nil) { [weak self] _ in
guard let `self` = self else {
return
}
self.fetchTrips()
}
}
In the deinit method of your class, make sure to remove the observer:
deinit { NotificationCenter.default.removeObserver(fetchTripsNotification as Any) }
In your class B (the poster of the notification) trigger the notification like usually:
NotificationCenter.default.post(name: .needsToFetchTrips, object: nil)

Is key-value observation (KVO) available in Swift?

If so, are there any key differences that weren't otherwise present when using key-value observation in Objective-C?
You can use KVO in Swift, but only for dynamic properties of NSObject subclass. Consider that you wanted to observe the bar property of a Foo class. In Swift 4, specify bar as dynamic property in your NSObject subclass:
class Foo: NSObject {
#objc dynamic var bar = 0
}
You can then register to observe changes to the bar property. In Swift 4 and Swift 3.2, this has been greatly simplified, as outlined in Using Key-Value Observing in Swift:
class MyObject {
private var token: NSKeyValueObservation
var objectToObserve = Foo()
init() {
token = objectToObserve.observe(\.bar) { [weak self] object, change in // the `[weak self]` is to avoid strong reference cycle; obviously, if you don't reference `self` in the closure, then `[weak self]` is not needed
print("bar property is now \(object.bar)")
}
}
}
Note, in Swift 4, we now have strong typing of keypaths using the backslash character (the \.bar is the keypath for the bar property of the object being observed). Also, because it's using the completion closure pattern, we don't have to manually remove observers (when the token falls out of scope, the observer is removed for us) nor do we have to worry about calling the super implementation if the key doesn't match. The closure is called only when this particular observer is invoked. For more information, see WWDC 2017 video, What's New in Foundation.
In Swift 3, to observe this, it's a bit more complicated, but very similar to what one does in Objective-C. Namely, you would implement observeValue(forKeyPath keyPath:, of object:, change:, context:) which (a) makes sure we're dealing with our context (and not something that our super instance had registered to observe); and then (b) either handle it or pass it on to the super implementation, as necessary. And make sure to remove yourself as an observer when appropriate. For example, you might remove the observer when it is deallocated:
In Swift 3:
class MyObject: NSObject {
private var observerContext = 0
var objectToObserve = Foo()
override init() {
super.init()
objectToObserve.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext)
}
deinit {
objectToObserve.removeObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &observerContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
// do something upon notification of the observed object
print("\(keyPath): \(change?[.newKey])")
}
}
Note, you can only observe properties that can be represented in Objective-C. Thus, you cannot observe generics, Swift struct types, Swift enum types, etc.
For a discussion of the Swift 2 implementation, see my original answer, below.
Using the dynamic keyword to achieve KVO with NSObject subclasses is described in the Key-Value Observing section of the Adopting Cocoa Design Conventions chapter of the Using Swift with Cocoa and Objective-C guide:
Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects. You can use key-value observing with a Swift class, as long as the class inherits from the NSObject class. You can use these three steps to implement key-value observing in Swift.
Add the dynamic modifier to any property you want to observe. For more information on dynamic, see Requiring Dynamic Dispatch.
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
Create a global context variable.
private var myContext = 0
Add an observer for the key-path, and override the observeValueForKeyPath:ofObject:change:context: method, and remove the observer in deinit.
class MyObserver: NSObject {
var objectToObserve = MyObjectToObserve()
override init() {
super.init()
objectToObserve.addObserver(self, forKeyPath: "myDate", options: .New, context: &myContext)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &myContext {
if let newValue = change?[NSKeyValueChangeNewKey] {
print("Date changed: \(newValue)")
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
deinit {
objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext)
}
}
[Note, this KVO discussion has subsequently been removed from the Using Swift with Cocoa and Objective-C guide, which has been adapted for Swift 3, but it still works as outlined at the top of this answer.]
It's worth noting that Swift has its own native property observer system, but that's for a class specifying its own code that will be performed upon observation of its own properties. KVO, on the other hand, is designed to register to observe changes to some dynamic property of some other class.
(Edited to add new info): consider whether using the Combine framework can help you accomplish what you wanted, rather than using KVO
Yes and no. KVO works on NSObject subclasses much as it always has. It does not work for classes that don't subclass NSObject. Swift does not (currently at least) have its own native observation system.
(See comments for how to expose other properties as ObjC so KVO works on them)
See the Apple Documentation for a full example.
Both yes and no:
Yes, you can use the same old KVO APIs in Swift to observe Objective-C objects.
You can also observe dynamic properties of Swift objects inheriting from NSObject.
But... No it's not strongly typed as you could expect Swift native observation system to be.
Using Swift with Cocoa and Objective-C | Key Value Observing
No, currently there is no builtin value observation system for arbitrary Swift objects.
Yes, there are builtin Property Observers, which are strongly typed.
But... No they are not KVO, since they allow only for observing of objects own properties, don't support nested observations ("key paths"), and you have to explicitly implement them.
The Swift Programming Language | Property Observers
Yes, you can implement explicit value observing, which will be strongly typed, and allow for adding multiple handlers from other objects, and even support nesting / "key paths".
But... No it will not be KVO since it will only work for properties which you implement as observable.
You can find a library for implementing such value observing here:
Observable-Swift - KVO for Swift - Value Observing and Events
Yes.
KVO requires dynamic dispatch, so you simply need to add the dynamic modifier to a method, property, subscript, or initializer:
dynamic var foo = 0
The dynamic modifier ensures that references to the declaration will be dynamically dispatched and accessed through objc_msgSend.
An example might help a little here. If I have an instance model of class Model with attributes name and state I can observe those attributes with:
let options = NSKeyValueObservingOptions([.New, .Old, .Initial, .Prior])
model.addObserver(self, forKeyPath: "name", options: options, context: nil)
model.addObserver(self, forKeyPath: "state", options: options, context: nil)
Changes to these properties will trigger a call to:
override func observeValueForKeyPath(keyPath: String!,
ofObject object: AnyObject!,
change: NSDictionary!,
context: CMutableVoidPointer) {
println("CHANGE OBSERVED: \(change)")
}
In addition to Rob's answer. That class must inherit from NSObject, and we have 3 ways to trigger property change
Use setValue(value: AnyObject?, forKey key: String) from NSKeyValueCoding
class MyObjectToObserve: NSObject {
var myDate = NSDate()
func updateDate() {
setValue(NSDate(), forKey: "myDate")
}
}
Use willChangeValueForKey and didChangeValueForKey from NSKeyValueObserving
class MyObjectToObserve: NSObject {
var myDate = NSDate()
func updateDate() {
willChangeValueForKey("myDate")
myDate = NSDate()
didChangeValueForKey("myDate")
}
}
Use dynamic. See Swift Type Compatibility
You can also use the dynamic modifier to require that access to members be dynamically dispatched through the Objective-C runtime if you’re using APIs like key–value observing that dynamically replace the implementation of a method.
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
And property getter and setter is called when used. You can verify when working with KVO. This is an example of computed property
class MyObjectToObserve: NSObject {
var backing: NSDate = NSDate()
dynamic var myDate: NSDate {
set {
print("setter is called")
backing = newValue
}
get {
print("getter is called")
return backing
}
}
}
Currently Swift does not support any built in mechanism for observing property changes of objects other than 'self', so no, it does not support KVO.
However, KVO is such a fundamental part of Objective-C and Cocoa that it seems quite likely that it will be added in the future. The current documentation seems to imply this:
Key-Value Observing
Information forthcoming.
Using Swift with Cocoa and Objective-C
One important thing to mention is that after updating your Xcode to 7 beta you might be getting the following message:
"Method does not override any method from its superclass". That's because of the arguments' optionality. Make sure that your observation handler looks exactly as follows:
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer<Void>)
This may be prove helpful to few people -
// MARK: - KVO
var observedPaths: [String] = []
func observeKVO(keyPath: String) {
observedPaths.append(keyPath)
addObserver(self, forKeyPath: keyPath, options: [.old, .new], context: nil)
}
func unObserveKVO(keyPath: String) {
if let index = observedPaths.index(of: keyPath) {
observedPaths.remove(at: index)
}
removeObserver(self, forKeyPath: keyPath)
}
func unObserveAllKVO() {
for keyPath in observedPaths {
removeObserver(self, forKeyPath: keyPath)
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let keyPath = keyPath {
switch keyPath {
case #keyPath(camera.iso):
slider.value = camera.iso
default:
break
}
}
}
I had used KVO in this way in Swift 3. You can use this code with few changes.
Overview
It is possible using Combine without using NSObject or Objective-C
Availability: iOS 13.0+, macOS 10.15+, tvOS 13.0+, watchOS 6.0+, Mac Catalyst 13.0+, Xcode 11.0+
Note: Needs to be used only with classes not with value types.
Code:
Swift Version: 5.1.2
import Combine //Combine Framework
//Needs to be a class doesn't work with struct and other value types
class Car {
#Published var price : Int = 10
}
let car = Car()
//Option 1: Automatically Subscribes to the publisher
let cancellable1 = car.$price.sink {
print("Option 1: value changed to \($0)")
}
//Option 2: Manually Subscribe to the publisher
//Using this option multiple subscribers can subscribe to the same publisher
let publisher = car.$price
let subscriber2 : Subscribers.Sink<Int, Never>
subscriber2 = Subscribers.Sink(receiveCompletion: { print("completion \($0)")}) {
print("Option 2: value changed to \($0)")
}
publisher.subscribe(subscriber2)
//Assign a new value
car.price = 20
Output:
Option 1: value changed to 10
Option 2: value changed to 10
Option 1: value changed to 20
Option 2: value changed to 20
Refer:
https://developer.apple.com/documentation/combine
https://developer.apple.com/documentation/combine/receiving_and_handling_events_with_combine
https://developer.apple.com/documentation/combine/published
Another example for anyone who runs into a problem with types such as Int? and CGFloat?. You simply set you class as a subclass of NSObject and declare your variables as follows e.g:
class Theme : NSObject{
dynamic var min_images : Int = 0
dynamic var moreTextSize : CGFloat = 0.0
func myMethod(){
self.setValue(value, forKey: "\(min_images)")
}
}