KVO

KVO: key-value Observing, a mechanism for Observing field Value changes by adding observers to a target object.

The principle of

Its underlying principles are implemented through the Isa mashup of Runtime.

Suppose we now need to use a class called KVOObserve to listen for changes to the Goods class price property. Here’s the code:

// Goods.h
@interface Goods : NSObject
@property (nonatomic, assign) int price;
@end

// KVOObserve.m
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"object: %@, keyPath:%@, change: %@", object, keyPath, change);
}

// main
KVOObserve *observer = [KVOObserve new];
Goods *goods = [Goods new];
[goods addObserver:observer forKeyPath:@"price" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
goods.price = 10;
Copy the code

When goods. Price = 10; KVOObserve’s callback method then prints the information:

object: <Goods: 0x100530310>, keyPath:price, change: {
    kind = 1;
    new = 10;
    old = 0;
}
Copy the code

After adding an observer to goods, the Runtime dynamically generates a class named NSKVONotifying_Goods to which goods’ ISA points. The superclass of NSKVONotifying_Goods points to the Goods class.

The above conclusion can be verified with the following code:

NSLog(@" before using KVO: %@", object_getClass(goods)); [goods addObserver:observer forKeyPath:@"price" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL]; NSLog(@" after using KVO: %@", object_getClass(goods)); // Print Goods before KVO: Goods after KVO: NSKVONotifying_GoodsCopy the code

As you can see from the print, after adding the observer, the GOODS isa points to NSKVONotifying_Goods.

NSKVONotifying_Goods

The goods ISA points to NSKVONotifying_Goods. What methods does NSKVONotifying_Goods contain? This can be obtained with the following code:

Goods *goods = [Goods new]; // Get the list of methods for goods before adding the observer, i.e. the list of methods for goods unsigned int count; Method *methodList = class_copyMethodList(object_getClass(goods), &count); for (int i = 0; i < count; i++) { Method method = methodList[i]; NSString *methodName = NSStringFromSelector(method_getName(method)); NSLog(@"method: %@", methodName); } free(methodList); [goods addObserver:observer forKeyPath:@"price" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL]; // Get the list of methods after the goods observer is added, Method *methodList2 = class_copyMethodList(object_getClass(goods), &count); for (int i = 0; i < count; i++) { Method method = methodList2[i]; NSString *methodName = NSStringFromSelector(method_getName(method)); NSLog(@"methodName: %@", methodName); } free(methodList2);Copy the code

Here is the print:

Method: price Method: setPrice: after KVO methodName: setPrice: methodName: class methodName: dealloc methodName: _isKVOACopy the code

Goods and NSKVONotifying_Goods

It can be learned from printing that the Goods method list includes get and set methods. NSKVONotifying_Goods has setPrice:, class, dealloc, _isKVOA in the list of methods.

Now that we have a list of methods for NSKVONotifying_Goods, look at the implementation of setPrice:.

_NSSetXXXValueAndNotify

Get setPrice:

[goods addObserver:observer 
    forKeyPath:@"price" 
    options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld 
    context:NULL];
NSLog(@"%p", [goods methodForSelector:@selector(setPrice:)]); 
Copy the code

After obtaining the memory address of setPrice: method, LLDB debugging is used to obtain the method implementation:

The setPrice: method calls a method called _NSSetIntValueAndNotify.

The following three methods are called internally by _NSSetIntValueAndNotify:

  • (void)willChangeValueForKey:(NSString *)key
  • [super setPrice: xx]
  • (void)didChangeValueForKey:(NSString *)key
    • Internally invoke the observer’s callback method

Through the above method call, the KVO value monitoring mechanism is completed.

KVO listening process:

Manually trigger KVO

To trigger KVO manually, call willChangeValueForKey and didChangeValueForKey:

[goods willChangeValueForKey:@"price"];
[goods didChangeValueForKey:@"price"];
Copy the code

Turn off automatic trigger

When we want to control when certain conditions to trigger the KVO, + (BOOL) can be rewritten automaticallyNotifiesObserversOfxxx way to implement.

For example, in some business scenarios we trigger KVO only if the price is different from the previous value:

/ / KVO don't trigger automatically + (BOOL) automaticallyNotifiesObserversOfPrice {return NO; } - (void)setPrice:(int)price { if (_price == price) { return; } [self willChangeValueForKey:@"price"]; _price = price; [self didChangeValueForKey:@"price"]; } // Goods. Price = 10; goods.price = 10;Copy the code

Usage scenarios

  • Listen for changes in model property values to update the UI.

Precautions when using

  • The keyPath of the callback function cannot be empty because it will crash.
  • Adding and removing observers should correspond one to one. Removing observers multiple times will cause a crash.
  • Because KVO is synchronous behavior, if the property value is modified in the child thread, the callback function is executed in the corresponding child thread. Because it is synchronous, it is not recommended in multithreaded situations.

KVO, agency and notice

All three mechanisms are designed to handle interactions between objects. The relationship between proxy objects is one-to-one, whereas the relationship between KVO and notifications is many-to-many.

The advantage of the proxy is that it is logical and easy to debug. If the protocol is followed and the proxy method is not implemented, there will be compilation ⚠️. The disadvantage is the amount of code. A common usage scenario is the interaction between controls.

The advantage of KVO is that the code is clean and simple, and you can simply add observers and remove observers when appropriate to implement listening. Disadvantage is to use more complex, code needs to pay attention to the specification.

Notifications also have the advantage of clean code. The disadvantage is that debugging is not very good debugging 🤣.

KVC

Key-value coding (KVC). Store values and values through an official set of APIS.

The values

Value by key - valueForKey: // Value by keyPath - valueForKeyPath: // Call -valueForUndefinedKey when the key passed by valueForKey cannot be found:Copy the code

API examples:

// Car.h @interface Car : NSObject @property (nonatomic, copy) NSString *name; @end // Goods.h @interface Goods : NSObject @property (nonatomic, assign) int price; @property (nonatomic, strong) Car *car; @end [goods valueForKey:@"price"]; [goods valueForKeyPath:@"car.name"]; [goods valueForKey:@"abc"]; // Since Goods has no ABC attribute, this clause crashes and the valueForUndefinedKey method is calledCopy the code

The underlying method call flowchart for valueForKey:

The assignment

-setValue :forKey: // setValue:forKeyPath -setvalue :forKeyPath: // When setValue:forKey: Incoming call key can't find the time - setValue: forUndefinedKey: / / when setValue: forKey: incoming value is nil when calling - setNilValueForKey:Copy the code

Examples of API usage:

[goods setValue:@10 forKey:@"price"]; [Goods setValue:@" Geely "forKeyPath:@"car.name"]; SetNilValueForKey: [goods setValue:nil forKey:@"price"]; / / ABC without this attribute, collapse, call setValue: forUndefinedKey: [goods setValue: @ 20 forKey: @ "ABC");Copy the code

SetValue :forKey:

KVC with KVO

  • Does using KVC assignment trigger KVO?

Is triggered because the nature of KVC is to call the set method to modify the property value. The essence of KVO is to override the set method of the listened object.