How to view and debug active key value observers - ios

Typical story... inherited a buggy app from a coder that left a year ago.
App uses some funky transition plugins; namely, iCarousel, MPFFlipTransition.
App pops separate browser tabs.
Error is thrown when the browser or a browser tab is closed
Somewhere an observer is getting leaked.
I've added the following code to the class in question, with no luck:
(void)dealloc {
// implement dealloc method/remove
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
How do I trace the app to find where this observer is being set? Can I observe specific memory locations? Can I somehow view the key/value pairs? Thx, Keith <3
Error thrown:
2014-10-03 12:33:20.938 myApp[5299:60b] An instance 0x145661f0 of class DesktopBrowserVC was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x14547300> (
<NSKeyValueObservance 0x14547210: Observer: 0x145550a0, Key path: title, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x14547320>

You could easily find where any observers are added for that key path by searching your project (Cmd+Shift+F) for the string forKeyPath:#"title"
You can also investigate what observers are currently observing your any object at any time by calling the following method:
[NSObject observationInfo]
And as #AaronWojnowski said, make sure you're not confusing Key-Value Observers with NSNotificationCenter observers!

Related

how does "idleTimerDisabled" work in didFinishLaunchingWithOptions?

I'm not sure how does the below code work in the didFinishLaunchingWithOptions in appdelegate ?
[[UIApplication sharedApplication] addObserver: self forKeyPath: #"idleTimerDisabled" options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context: nil];
what does it exactly do with "idleTimerDisabled" ?
thanks
Apple Documentation for idleTimerDisabled
The default value of this property is NO. When most apps have no touches as user input for a short period, the system puts the device into a "sleep” state where the screen dims. This is done for the purposes of conserving power. However, apps that don't have user input except for the accelerometer—games, for instance—can, by setting this property to YES, disable the “idle timer” to avert system sleep.
By assigning true to this value, iOS won't dim the screen and lock the iPhone when user doesn't make any action (touches, press, scroll, etc...). Example of this can be found in games vs other normal apps. Games make your iPhone awake for a lot longer than other apps.
For
[[UIApplication sharedApplication] addObserver: forKeyPath: options: context:]
This is an Objective-c key value observing aka KVO.
What your code means is that, when someone assigns or make changes to UIApplication.sharedApplication.idleTimerDisabled with an arbitrary value, true or false in this case, you wish to receive an invocation about the assignment or change in [self observeValueForKeyPath: ofObject: change: context:] method signature.
The option NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew means, you want to receive more information about changed old value and new value in the change dictionary which you can access later in the observing method.
For more information about KVO, checkout this post by NSHipster.
AppCoda also has a nice explanation for this.
Conclusion
What your line of code means is, you want to receive a notification about the changes made to UIApplication.shared.idleTimerDisabled attribute in KVO observing method and you want to access old value and new value through change dictionary.

Swift 4 KVO block crash: observed object deallocated while observer was still registered

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?

AVPlayer removing observer crash in Swift 2.2

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

Crashreport indicates KVO connection between object and parameter which was never initiated in code

Recently I have gotten some very strange crashes on HockeyApp:
Application Specific Information: Terminating app due to uncaught
exception 'NSInternalInconsistencyException', reason: 'An instance
0x13ef90530 of class FPPhoto was deallocated while key value observers
were still registered with it. Current observation info:
NSKeyValueObservationInfo 0x13e9e8bb0 ( NSKeyValueObservance
0x13ef36810: Observer: 0x13e5a95f0, Key path: fractionCompleted,
Options: New: YES, Old: NO, Prior: NO> Context: 0x0, Property:
0x13ef795d0 )'
I understand the basis of the crashreport and this can very well be true as I have KVO observers all over the place in my app, also connected to objects of type FPPhoto
The strange thing I wonder about is the name of the parameter: (Key path: fractionCompleted) We actaully have a parameter called fractionCompleted, but that is in a totally different place in the application. We add KVO to fractionCompleted to track upload progress. fractionCompleted KVO listener is though never added to any object of kind FPPhoto in the code.
I guess either it's the crash report that got the parameter/class names mixed up or can it be worse, -> some kind of memory mixup at runtime to make KVO attach to the wrong object?
Any ideas how this can happen?
You should consider adding two symbolic breakpoints on:
-[NSNotificationCenter addObserver:selector:name:object:]
and
-[NSNotificationCenter addObserverForName:object:queue:usingBlock:]
With the condition:
(BOOL)[#"fractionCompleted" isEqualToString:(id)$r8]
and
(BOOL)[#"fractionCompleted" isEqualToString:(id)$rdx]
Then set an action as a Debugger Command:
po $rdx
and
po $rcx
If you're curious about why the registers change, that is because OS X follows the System V ABI for register calling conventions.
Arguments for a method go into:
rdi, rsi, rdx, rcx, r8, r9
In that argument order. Because Objective-C messages get compiled into objc_msgsend(receiver, selectorname, argument1, argument2, argument3, argument4)
We have to change the registers being compared because those 2 -addObserver signatures differ in the location of their arguments for the notification name and the observer object.
Using these breakpoint will help you identify which objects get registered as an observer, so you can make sure they are deallocated & remove observation properly.
EDIT: Just realized that you are not using NSNotificationCenter, just KVO. But I don't want to delete my answer, so I'm just going to leave it. It's still applicable, you just need to change the symbol you break on to - addObserver:forKeyPath:options:context:, and you will need to add more conditions because otherwise you will hit that breakpoint 10 times a second.

mergeChangesFromContextDidSaveNotification Consumes Memory

I have two NSManagedObjectContext, one for ui and one for background tasks. I'm trying to merge changes to UIcontext whenever the background one changed. But whenever I call
mergeChangesFromContextDidSaveNotification:notification
It just start eating memory (will goes up to 1GB on simulator) and cause a crash.
of course I setup a notification:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(coreUpdateFromApp:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
and also tried doing the merge in main thread, etc. No luck!
I found out that URIRepresentation is causing the issue. For some reason it's keep being called. (by apple's code not mine)
Note that I let it run for under a sec and it uses 64.95MB it will grow pretty fast with same call tree. If I keep it running it would crash the osx as well!
The problem is object:nil. You are listening to an endless echo of notifications.
You need to specify a specific context object to listen to notifications from.
The Problem here is, Since Google Analytics also using core-data we are intercepting the endless notifications fired by Google Analytics as well.
Setting object to non-nil value didn't work for me. Found another way and it worked for me as magic.
Inside your observer I have selector method as below
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(managedObjectContextDidSave(_:)), name: NSManagedObjectContextDidSaveNotification, object: nil)
and,
func managedObjectContextDidSave(notification: NSNotification) {
if notification.object?.persistentStoreCoordinator != self.persistentStoreCoordinator {
return
}
//do remaining task here.
}

Resources