Welkin's Secret Garden

Observer Pattern for Cocoa(2)

The answers are Key-Value-Observing and NSNotification .

Let’s concentrate on the first one.

Key-Value-Observing

It’s declared in the documentation that:

Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects. —— developer.apple.com

Key-Value observing (KVO) is an alternative solution, and even a more convenient solution. Learning from the documentation, once the properties changes, the observer will be notified, so it help reduce our amount of work greatly.

How to use?

I do not want to focus too much on how to use it and I simply hope to show how concise it is.

There are only two steps to use KVO.

  • Add observers to a subject.
1
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
  • Implement a callback method in observer.
1
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context

That is all.

How this works?

Scanning the documentation again, I come across this:

Automatic key-value observing is implemented using a technique called isa-swizzling.

When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. —— developer.apple.com

In order to understand what it means, I try printing the isa of an object of Tom after adding an observer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[tom addObserver:jerry forKeyPath:@"state" options:NSKeyValueObservingOptionNew context:nil];

NSLog(@"added:");
NSLog(@"class: %@", tom.class);
NSLog(@"superClass: %@", tom.superclass);
NSLog(@"class: %s", object_getClassName(tom));
NSLog(@"superClass: %@", class_getSuperclass(NSClassFromString([NSString stringWithCString:object_getClassName(tom) encoding:NSUTF8StringEncoding])));

[tom removeObserver:jerry forKeyPath:@"state"];

NSLog(@"removed:");
NSLog(@"class: %@", tom.class);
NSLog(@"superClass: %@", tom.superclass);
NSLog(@"class: %s", object_getClassName(tom));
NSLog(@"superClass: %@", class_getSuperclass(NSClassFromString([NSString stringWithCString:object_getClassName(tom) encoding:NSUTF8StringEncoding])));

Then I get the result:

1
2
3
4
5
6
7
8
9
10
Key-Value-Observing[13844:591228] added:
Key-Value-Observing[13844:591228] class: Tom
Key-Value-Observing[13844:591228] superClass: NSObject
Key-Value-Observing[13844:591228] class: NSKVONotifying_Tom
Key-Value-Observing[13844:591228] superClass: Tom
Key-Value-Observing[13844:591228] removed:
Key-Value-Observing[13844:591228] class: Tom
Key-Value-Observing[13844:591228] superClass: NSObject
Key-Value-Observing[13844:591228] class: Tom
Key-Value-Observing[13844:591228] superClass: NSObject

Oops, isa has pointed to a class named NSKVONotifying_Tom and its super class has pointed to Tom !

Thus let’s guess that after adding observers, a new class will be created, subclassing the subject’s class, which overrides the setter of the observed ivar. Then set the isa of the observed subject to the subclass thus when the setter method is called, the actual implementation is that overrided in the subclass.

Then when the observer is removed, the isa will also be changed to the original one. It is funny that Apple hope to cover up what they has done and re-write the class and superclass to return what they used to be.

This is for today.