I have a video app that I built a while back in Swift 1 and I've been trying to migrate to Swift 2.2. It all (finally) works apart from a weird crash to do with observers.
func removeObservers()
{
print("REMOVING OBSERVERS")
if ( !self.is_image && self.player != nil ) {
if (self.player?.observationInfo != nil) {
self.player?.removeObserver(self, forKeyPath: "currentItem.status")
self.player?.removeObserver(self, forKeyPath: "readyForDisplay")
}
}
NSNotificationCenter.defaultCenter().removeObserver(self)
}
This worked previously using SwiftTryCatch but with the lines in place crashes with "'Cannot remove an observer for the key path "readyForDisplay" from because it is not registered as an observer.'" OR with that an observer is registered on a deallocated object if I comment it out.
If I add a do { } catch {} to it I get an error that "this does not throw" and it just crashes the same. How do I go about putting this in some form of try-catch format?
In Swift 2, the libs got annoyingly strict about errors that are truly unexpected (which throw) versus errors that the programmer could have prevented (which do not throw, but just crash your app).
(I’m not a fan of this distinction, or at least not of all the specific decisions Apple made about which errors fall in which category. The JSON API verges on the nonsensical in this department. But…we work with the API we’ve got.)
The NSKeyValueObserving docs say:
It is an error to call removeObserver:forKeyPath: if the object has not been registered as an observer.
“It is an error” is Apple code for “you are responsible for never doing this, and if you do, your app will crash in an uncatchable way.”
In these situations, there is usually an API call you can make to check the validity of the thing you’re about to do. However, AFAIK, there’s no KVO API call you can make to ask, “Is X observing key path Y of object Z?” That means you have three options:
Figure out why you’re trying to remove an observer from something you’re not observing, and prevent that using your program’s own internal logic.
Keep a weak instance var for “player I’m observing,” and check that for a match before attempting to remove the observer.
Add self as an observer before removing it. (I’m pretty sure that a redundant add is OK.)
Since you are making a call removeObserver(self) at the end of the method, why cant you uncomment above code? Because removeObserver(self) removes all the observers if registered any. I hope this solves your issue.
NSNotificationCenter.defaultCenter().removeObserver(self)
status is a property of either AVPlayer or AVPlayerItem.
readyForDisplay is a property of AVPlayerLayer
Related
I am adding a number of observers in my viewController -- applicationWillResignActive, applicationDidEnterBackground, and many others. I want to remove self as observer to all registered notifications in one line. My question is whether the following line is enough to do that, or are there issues with this code?
deinit {
NotificationCenter.default.removeObserver(self)
}
#Sh_Khan is right:
NotificationCenter.default.removeObserver(self)
You can get even further, as mentioned in the Apple Documentation:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method.
So I'm working with this in an app right now and the answer might not be as straightforward.
In the documentation, it does state that for iOS 9 and above you are no longer required to explicitly remove the observer in the deinit/dealloc methods for objects. https://developer.apple.com/documentation/foundation/notificationcenter/1413994-removeobserver
However, it appears that this is only true for selector based notification observers. I'll be referencing this blog post: https://oleb.net/blog/2018/01/notificationcenter-removeobserver/.
If you are using block based observers you must still manually remove the observers.
addObserver(forName:object:queue:using:)
The best general way to do this is to capture the tokens in an array, append them when you add the observer and use them for removal when you deinit/dealloc or otherwise need to remove observer behavior for your object.
in your VC/object properties create an array to store observer 'tokens'
var notifObservers = [NSObjectProtocol]()
Register for a block based notification by capturing the function return object and storing it as a token
let observer = NotificationCenter.default.addObserver(forName: , object: , queue:) { [weak self] notification in
// do a thing here
}
notifObservers.append(observer)
Removal
for observer in notifObservers {
NotificationCenter.default.removeObserver(observer)
}
notifObservers.removeAll()
Yes
NotificationCenter.default.removeObserver(self)
this line is sufficient to remove the vc observation as long as all are added with
NotificationCenter.default.addObserver
I'm using the new Swift 4 API and I'm getting the KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED in a non-deterministic manner. Was anyone able to fix it? The code is pretty simple creating a KVO and storing it in a local variable.
private var rateObservation: NSKeyValueObservation?
rateObservation = player.observe(\AVQueuePlayer.rate, options: [.initial, .new]) { (_, change) in
observer(change.newValue)
}
The answer doesn't make sense to me. But it is the only way that made it work for me.
deinit {
rateObservation?.invalidate()
}
Doesn't make sense because the documentation of the new observation clearly says that we don't need to unregister the notification and that works for almost most of the time but sometimes I got the mentioned exception and when I explicitly invalidated the observer it never throw the exception.
I started developing my app recently with iOS 11 as target version, because that was the default value. I have now lowered the version to 9.3, because of reasons.
The app is pure swift 4, using the new KVO block thing.
I fixed the few compile-time errors I had with safeAreaInsets and whatnot, and the app built successfully. A quick job. Nice.
I tried running it on an iPhone 7 iOS 10.3.1 Simulator, and lord - it was a train wreck. I guess UITableViewAutomaticDimension wasn't really a thing back in the days.
Anyway, I have fixed most of the layout-issues, but now I'm stuck with a few hard crashes. Everywhere I've used this new KVO it crashes when I navigate back.
My navigation-pushed ViewController is KVO-listening to a field inside an object it holds. When I pop the navigation, the viewController and the object is deallocated, in that order, and the app crashes, giving me this error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7fdf2e724250 of class MyProject.MyObject was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x60800003fd80> (
<NSKeyValueObservance 0x610000050020: Observer: 0x61000006f140, Key path: isSelected, Options: <New: NO, Old: NO, Prior: NO> Context: 0x0, Property: 0x6180000595f0>
)'
As far as I can tell, it says that the observed object MyObject was deallocated while there was someone observing the variable isSelected.
This is the oberservation-code in MyViewController that observes MyObject.
var observations:[NSKeyValueObservation] = []
func someFunction() {
observations.removeAll()
let myObject = MyObject(/*init*/)
self.myObject = myObject
observations.append(myObject.observe(\.isSelected, changeHandler: { [weak self] (object, value) in
//Do stuff
}))
}
I was under the impression that this new magic KVO-block-style would solve world peace, but apparently that only applies to iOS 11.
Now, I have tried a few things, but I can't get it to not crash. It happens every single time, and I don't understand why.
Since the crash logs tells me that the observed object is being deallocated while an object is observing it, but I also know that the observing object is deallocated before the observed object, I have tried doing this in the observer:
//In MyViewController
deinit {
observations.forEach({$0.invalidate()})
observations.removeAll()
print("Observers removed")
}
But this doesn't help. I have also done this:
//In MyObject
deinit{
print("MyObject deinit")
}
And when I do the thing - I get the following output:
>Observers removed
>MyObject deinit
>WORLD WAR 5 STACK TRACE
I have also tried
//In MyViewController
deinit{
self.myObject.removeObserver(self, forKeyPath: "isSelected")
}
But I get the output saying Can't remove because it is not registered as an observer. So I guess MyViewController isn't actually the observer that gets attached when using this new KVO.
Why didn't any of this work? Where and when do I have to remove the observers in < iOS11?
It looks like you're running into a bug that I reported about a year ago, but which unfortunately has received very little attention:
https://bugs.swift.org/browse/SR-5752
Since this bug hasn't bitten me in a while, I had hoped that it'd been fixed in the Swift overlay, but I just tried copying my code from the bug report into an iOS project and running it in the 10.3.1 simulator, and sure enough, the crash came back.
You can work around it like this:
deinit {
for eachObservation in observations {
if #available(/*whichever version of iOS fixes this*/) { /* do nothing */ } else {
self.removeObserver(eachObservation, forKeyPath: #keyPath(/*the key path*/))
}
eachObservation.invalidate()
}
observations.removeAll()
}
Make sure you only do this on iOS versions affected by the bug, because otherwise you'll remove an observation that's already been removed, and then that will probably crash. Isn't this fun?
From Apple Documents pausesoncompletion
Because the completion handler is not called when this property is true, you cannot use the animator's completion handler to determine when the animations have finished running. Instead, you determine when the animation has ended by observing the isRunning property.
But i found observe isRunning not work. util i wathched WWDC 2017 - Session 230-Advanced Animations with UIKit ,i konwed i should observe running
//not work
animator.addObserver(self, forKeyPath: "isRunning", options: [.new], context: nil)
//this work
animator.addObserver(self, forKeyPath: "running", options: [.new], context: nil)
My Question is: where can i find the excatly keypath, not only this case. Thx~
In Swift it's recommended to use the block-based KVO API (available as of Swift 4) which allows you to observe a property in a type safe and compile-time checked manner:
// deinit or invalidate the returned observation token to stop observing
let observationToken = animator.observe(\.isRunning) { animator, change in
// Check `change.newValue` for the new (optional) value
}
Note that the key path is \.isRunning because the property on UIViewPropertyAnimator in Swift is called isRunning.
This both has the benefit that you don't have to know how the string for a given property is spelled and that the changed value has the same type as the observed property.
Note that in Objective-C this API is not available, so the corresponding Objective-C documentation ask you to observe the "running" property. This is because in Objective-C the property is called "running" (but has a getter called "isRunning").
A little late to the party, but the currently accepted answer doesn't exactly answer the question, so let me go ahead.
The discrepancy comes from using different names for ObjC and Swift. Use #keyPath(Type.propertyName)
So for your specific case it would be #keyPath(UIViewPropertyAnimator.isRunning)
Try printing the result. It will correctly show "running"
When app starts some preliminary process take place. Sometimes it is done quickly in some second, and sometimes It does not end, but without any error it hung up.
I.e. at launch client always fetch the last serverChangedToken. and sometimes it just hung up it does not complete. I am talking about production environment, developer works well. So this route get called, but some times it does not finishes. Any idea why? I do not get any error, timeout.
let fnco = CKFetchNotificationChangesOperation(previousServerChangeToken: nil)
fnco.fetchNotificationChangesCompletionBlock = {newServerChangeToken, error in
if error == nil {
serverChangeToken = newServerChangeToken
dispatch_sync(dispatch_get_main_queue(), {
(colorCodesInUtility.subviews[10] ).hidden = false
})
} else {
Utility.writeMessageToLog("error 4559: \(error!.localizedDescription)")
}
dispatch_semaphore_signal(sema)
}
defaultContainer.addOperation(fnco)
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)
I know it is not recommended to use semaphores to control flow of the CloudKit method calls.
Do you think the last two line can be swapped? First dispatch_semaphore_wait and then addOperation be called?
Strange that app worked for iOS 8, this bug arise only in iOS 9.
Adding the following line of code will probably solve your problem:
queryOperation.qualityOfService = .UserInteractive
In iOS 9 Apple changed the behavior of that setting. Especially when using cellular data you could get the behaviour you described.
The documentation states for the .qualityOfService this:
The default value of this property is NSOperationQualityOfServiceBackground and you should leave that value in place whenever possible.
In my opinion this is more a CloudKit bug than a changed feature. More people have the same issue. I did already report it at https://bugreport.apple.com