0

We have had this weird issue. When we instantiated an object, we also instantiate a property that belongs to that object:

-(instancetype)init
{
   self = [super init];
   if (self) {
     [self setDocument];
   }
   return self;
}

-(void)setDocument:
{
   _flatGraphicsArrayController = [[NSArrayController alloc] initWithContent:doc.flattenedObjects];
}

...and occasionally a EXC_BAD_ACCESS happens at the setting of _flatGraphicsArrayController

The call stack:
enter image description here

This crash has been determined to be caused by sending that NSKeyValueNotifyObserver message to a deallocated object, an object that appears to be observing changes to flatGraphicsArrayController

To me, this is very confusing because the object that owns this property is just being instantiated, so how could anything possibly be observing changes to the property?

Was somebody registered to observe a specific memory address (if that's how it works), and then the flatGraphicsArrayController somehow took that space in memory, while the observer was deallocated?

A O
  • 5,516
  • 3
  • 33
  • 68

1 Answers1

2

Some object (Object1) was added as an observer to another object (Object2). Sometime after that, both Object1 and Object2 were deallocated, but nothing ever removed Object1 as an observer of Object2. The relationship of key-value observers is kept outside of either object (because when KVO was added, no new instance variables could be added to NSObject for binary compatibility reasons, so it has to store its state in a side table).

KVO should complain about this at the time of deallocation of Object1. Check the console log.

Anyway, at some later time, you create your instance of NSArrayController. It happens to occupy the same address as Object2. This means it matches KVO's internal information about the observation relationship between Object1 and Object2. So, effectively, the defunct Object1 is now observing your array controller. When its properties are changed, it sends KVO change notifications to Object1. Of course, Object1 no longer exists. Depending on whether its address has been reused and whether that address is the base address of a new object or points somewhere within it, the result can be a crash or silent.

To fix this, you need to always remove KVO observations before either the observed or observing object is deallocated.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • Thanks for the answer; that's what the problem seemed like. What happens when an object of a different type occupies the memory space of the old Object2? – A O Oct 19 '15 at 14:28
  • You're welcome. I'm not sure what you're asking. It's quite likely that "an object of a different type occupies the memory space of the old Object2" in this case. That's the point. – Ken Thomases Oct 19 '15 at 15:09
  • So the KVO system doesn't care about the object that's being observed-- if it gets released and replaced by an object of a different type, and that new object changes, it still sends notifications? e.g, something is observing a Person object. Person gets released. A Dog object replaces it's place in memory. Dog object changes, does the original observer get a notification? Or would the original observer only get the notification if the Person object was replaced by another person? – A O Oct 19 '15 at 20:56
  • The type doesn't much matter, no. KVO does swizzle the class of observed objects in order to hook into their setters and other mutating accessors, but that's not the only way that objects will generate change notifications. They will do so when KVC is used to mutate them or if they explicitly call `-will/didChange...` methods, etc. You're focusing on the wrong thing. There's no point analyzing what KVO will do in an obviously broken scenario. Fix the failure to remove the observer before either object is deallocated and how KVO behaves when things are broken becomes irrelevant. – Ken Thomases Oct 19 '15 at 22:04
  • Roger. Was actually able to find the place where the observation failed to be removed. It was because a receiver was `nil` during the attempt to remove observer. But now I don't understand why the receiver became `nil`. I posted another question if you're interested: http://stackoverflow.com/questions/33224893/if-a-child-object-is-released-in-the-dealloc-of-a-parent-object-why-would-the-c Thanks again for your time here, though :) appreciate it – A O Oct 19 '15 at 22:18