Matt Rajca

A KVO Debugging Story

September 08, 2016

Pixen has a PXMask class that is used to represent a selection mask and keep track of the selected pixels on the canvas. This class has a hasSelection property that defines whether or not there is at least one pixel selected, and this value is observed in the UI layer that decides whether or not to draw the selection mask.

Recently, I found that the observer was firing on the order of hundreds of times per second in certain scenarios, so something was clearly wrong. The accessor is overriden as such to prevent KVO notifications from firing when the value does not in fact change:

- (void)setHasSelection:(BOOL)hasSelection
{
	if (_hasSelection == hasSelection) {
		return;
	}
	
	[self willChangeValueForKey:hasSelectionKey];
	_hasSelection = hasSelection;
	[self didChangeValueForKey:hasSelectionKey];
}

I found, however, that KVO notifications would fire regardless of whether the value of hasSelection actually changed. In other words, this short loop would trigger the KVO notification 10 times, even though the value of hasSelection only changes from NO to YES once:

for (NSInteger i = 0; i < 10; i++) {
	self.hasSelection = YES;
}

The first thing I did was ensure the setter method was actually taking the early return path for iterations 2-10 of the loop, and it was!

After some trial and error, I found that renaming the method to -changeHasSelection: fixed the issue. Moreover, if I kept the name as is and removed the calls to -{will|did}ChangeValueForKey:, the notifications were still being fired.

It appears as if Cocoa fires KVO notifications for setters that start with set or _set regardless of whether you invoke -{will|did}ChangeValueForKey: and regardless of whether the value actually changes. All other methods must explicitly call -{will|did}ChangeValueForKey:.

Runtime magic indeed!