KVO, a familiar noun, is simple to use.

But do you really understand it? Why does this thing exist? Do you understand the logic behind it? What kind of posture should we use to be considered elegant? That’s what this article will give you.

The official story

The implementation of KVO has not been officially announced, but we can see a note like this:

Key-Value Observing Implementation Details

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

The isa pointer, as the name suggests, points to the object’s class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.

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.

You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

The translation goes something like this:

Automatic key-value Observing is implemented by using a technology called ISA-Swizzling. As the name implies, the ISA pointer points to the object class that maintains the dispatch table. The dispatch table essentially contains Pointers to methods implemented by the class, as well as other data. When an observer registers an object's properties, the isa pointer to the observed object is modified to point to an intermediate class instead of the real class. Therefore, the value of the ISA pointer does not necessarily reflect the actual class of the instance. Never rely on isa Pointers to determine class membership. Instead, use the class method to determine the class of an object instance.Copy the code

To sum up, it probably expresses the following idea:

  • KVOThe full name is calledkey-value observing;
  • KVOIt’s using something calledisa-swizzlingWe don’t know how it works, but we know what it does, and here’s how it works;
  • This technology directly putsisaThe pointer was changed to point to an intermediate class instead of the object’s own class;
  • sinceisaThe pointer can be changed, so useisaNot for his ID. Close the door and open a window. UseclassMethod to establish the identity.

Let’s start with the official version.

Basic operation

When using KVO, we can usually do something like this:

Person * onePerson = [[Person alloc] init];
// Try to change the value of age
onePerson.age = 1;

// self listens on the age property of onePerson
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[onePerson addObserver:self forKeyPath:@"age" options:options context:nil];
// Try changing the age value again
onePerson.age = 10;
[onePerson removeObserver:self forKeyPath:@"age"];
Copy the code

The code above is in place for the ViewController. Obviously, we add a listener to the Person object, onePerson, for the property age. Observer is self(ViewController).

We can then implement the listening callback method:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey.id> *)change context:(void *)context {

    NSLog(@"Got!! Object %@ property %@ changed: %@", object, keyPath, change);

}
Copy the code

Ok, all set, let’s start the show.

Who does ISA point to?

Conventionally, there’s a break point. Where is the break point? Since we need to understand the change before and after adding KVO, the outage is added to addObserver, which is here:

Run, we print at breakpoint and after single steponePersonisa:

Obviously, the direction of isa does change. After addObserver, onePerson’s ISA points to a class called NSKVONotifying_Person, which we didn’t define ourselves, Instead, it is generated after we execute addObserver, which is a class created by the Runtime with the shadow of Person in the class name, or rather with a prefix.

So far, it appears that an intermediate class has been created, as officially stated, but it does not say who the new class is or how it relates to the original class. Look at the class name seems to be related.

There is a hearsay that the newly generated class is a subclass of the original class.

To verify this, look at the superclass in the same way:

All you see here are NSObject objects. Does that mean that the subclass argument is not true?

In fact, we know that the superclass may not be accurate, like what if it was rewritten? Here’s a more accurate way to get it, the same way:

As you can see, object_getClass() gets the same object as isa, and if you look at the Runtime source code, you will see that it is actually isa returned. We can see from the result of class_getSuperclass() that the parent class of NSKVONotifying_Person is indeed Person.

The new class is a subclass of the original class, which is named with the prefix NSKVONotifying_.

class

Superclass (NSKVONotifying_Person) {superclass (NSKVONotifying_Person);

And we know from the official version of class that it’s actually more accurate to get the category of an object by class. How do I understand it here?

Let’s do the same thing again:

As you can see, the output of class is identical to that of superclass. What does that mean? The class method has also been overridden.

So the question is, in fact, the result of class is not accurate, why the official recommendation?

As the saying goes, you only see what it wants you to see, and that’s what the authorities want.

In other words, the NSKVONotifying_Person class doesn’t really exist, it’s just a way of implementing KVO, making it look like the world is at peace while he does what he likes.

So, do you know?

setter

We have also heard that the implementation of KVO is ultimately implemented by overriding setter methods. Isn’t it?

Here we go again, same technique:

We still print the relevant information before and after the addObserver, which is the IMP address of the setter method and its related information.

It’s not hard to see that the implementation of the setter has really changed before and after this, because we know from its information that it’s calling the normal setAge: method, and then what? What the hell is Foundation _NSSetUnsignedLongLongValueAndNotify?

At least we know because this implements KVO notification before and after a value change, and UnsignedLongLong should depend on the data type of our property, and there should be a bunch of other methods like this, so let’s make sure.

Let’s change the datatype of age to double:

The keyword or function name is also pronounced as a change, corresponding to a double, so you can guess that there are actually a bunch of functions in Foundation that correspond to various data types. It is said that it can be found through the nm command, which I did not find in the latest SDK anyway, but it does not have any effect.

And of course, all of these functions do the same thing. What is it? It is at the same time as setAge that it is notified before and after setting the value.

We usually add the following code to the Person class:

- (void)willChangeValueForKey:(NSString *)key {
    NSLog(@"Got!! Will change value for key: %@", key);
    [super willChangeValueForKey:key];
}

- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"Got!! Did change value for key: %@", key);
    [super didChangeValueForKey:key];
}
Copy the code

We break the break point and run again:

We can obviously see that the Person willChangeValueForKey, didChangeValueForKey, was called after setAge: Method and ViewContriller observeValueForKeyPath: ofObject: change: context: method.

So, KVO really overwrites the setter method in order to pass willChangeValueForKey, didChangeValueForKey, before and after changing the property value. Notice, and trigger observeValueForKeyPath: ofObject: change: context: callback.

Manual triggerKVO

So now that we have willChangeValueForKey, didChangeValueForKey, what happens if we call it ourselves? There are no official restrictions on how we can call it, such as changing the code to something like this:

// self listens for p1's age property
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[onePerson addObserver:self forKeyPath:@"age" options:options context:nil];

// Comment temporarily through setters to change the value of age
// onePerson.age = 10;

// Call manually
[onePerson willChangeValueForKey:@"age"];
[onePerson didChangeValueForKey:@"age"];
Copy the code

I’ve commented out the pass heresetterThe method ofageProperty assignment, let’s run and see.

First of all, there is nothing wrong with this manual call, and it is exactly the same as the previous log (although there is a slight difference, I will explain later).

But the problem is in the same, the question is: observeValueForKeyPath: ofObject: change: context: how to trigger?

This is confirmed by further comment willChangeValueForKey: didChangeValueForKey: Method triggers, meaning that after KVO rewrite setter, not inside the rewrite the setter methods of trigger observeValueForKeyPath: ofObject: change: context: The callback trigger actually happens inside didChangeValueForKey:.

So even though we can manually call the above method, but still need to be careful, because it will trigger the observeValueForKeyPath: ofObject: change: context: The point is that if you look at the log after the callback, the value hasn’t changed. This is probably a problem. This is actually a false trigger.

KVOPossible realization

At this point, we can actually look at the internal implementation of KVO, which looks something like this (just pseudo-code) :

- (void)setAge (NSUInteger)age { Here only saw the age _NSSetUnsignedLongLongValueAndNotify ()} - (void) willChangeValueForKey: (nsstrings *) key {[super willChangeValueForKey:key]; } - (void)didChangeValueForKey:(NSString *)key { [super didChangeValueForKey:key]; / / / trigger callbacks [observer observeValueForKeyPath: key ofObject: XXX change: context: XXX XXX]; } void _NSSetObjectValueAndNotify() { [self willChangeValueForKey:@"age"]; // Call the setter method of Person [super setAge:age]; [self didChangeValueForKey:@"age"]; }Copy the code

Elegant useKVO

We said to the above, in the properties of added KVO, after modify the attribute values will destroy method observeValueForKeyPath: ofObject: change: context:, that we add multiple properties after that? We need to add a bunch of if-else judgments to the above callback method, which is obviously not elegant.

There’s actually an elegant pose for using KVO, which comes from FB’s KVOController, right here.

We can also study the logic of its encapsulation of KVO, and we have gained a lot. We will talk about it later if we have the opportunity.

The last

Ok, above, hope we can gain something.