KVO

What is the KVO

KVO, full name of Key Value Observing, is an event notification mechanism provided by Apple. Allows an object to listen for changes to specific properties of another object and to receive an event when the change occurs. Observer mode

Because of the way KVO is implemented, it only works for properties, and objects that inherit from NSObject generally support KVO by default

KVO can listen for changes to individual attributes as well as collection objects. Collection objects contain NSArray and NSSet. The proxy object is obtained through methods such as KVC’s mutableArrayValueForKey:, and when the internal object of the proxy object changes, the method that KVC listens for is called back.

Basic use of KVO

There are three main steps

  • throughaddObserver:forKeyPath:options:context:Method register observer
    • Observer: An object that listens for property changes. This object must be implementedobserveValueForKeyPath:ofObject:change:context: Methods.
    • KeyPath: the name of the property to be observed. Be consistent with the name of the property declaration
    • Options: The callback method that receives the old or new value of the observed property, and the enum type. The system provides us with four methods
      • NSKeyValueObservingOptionOld: change contains the old value before the key change
      • NSKeyValueObservingOptionNew: change contains the new value after the key change
      • NSKeyValueObservingOptionInitial: change does not contain the value of key, kVO will notify immediately upon registration
      • NSKeyValueObservingOptionPrior: notifies the value once before it is changed, and notifies it again after it is changed, i.e., two notifications per change. NotificationIsPrior = 1; The change notification does not include notificationIsPrior and can be used in conjunction with the willChange manual notification
      • We can also use vertical lines to make multiple choicesNSKeyValueObservingOptionOld |. NSKeyValueObservingOptionNewSo change has both new and old
  • Observe object changes, callback methodobserveValueForKeyPath:ofObject:change:context:
    • KeyPath: properties of the observed object
    • Object: Indicates the object to be observed
    • Change: dictionary type, which holds the associated value and returns either the new value or the old value or noticationlsPrior = 1 based on the enumeration passed in options
    • Context: The value of the context passed in to register an observer
  • Can be called when the observer does not need to listenremoveObserver:forKeyPath:Method to remove the KVO, which we need to process before the observer disappears, or crash

Dealloc actually has a bit of a problem here

Notify part addObserver: we use the selector: name: object: automatically help us to delete the observer, so we don’t have to write their own [[NSNotificationCenter defaultCenter]removeObserver:self]; To delete the observer

However, I did not find the function whether KVO will automatically delete for us in the official document

I’m going to test it is there really a normal project is there anything wrong with it

If the observer is destroyed, the observed object is not destroyed ==(for example, we are observing a property in a singleton), and then a KVO message is generated, then an exception is thrown, EXC_BAD_ACCESS

So when we use KVO, it’s best to write dealloc, remove the observer [single removeObserver:self forKeyPath:@”height”];

    • -Blair: I’m an observer.
    • KeyPath: properties of the observed object

Call KVO manually

KVO cannot listen inside an array element, so we need to call KVO manually

KVO is called automatically when a property is changed. If you want to control the call timing manually, or if you want to implement a KVO property call yourself, you can do so through the methods provided by KVO.

  1. If you want to manually call or implement KVO yourself, you need to override the following methods. This method returns YES to allow the system to call KVO automatically, NO to disallow the system to call KVO automatically
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"name"]) {
        automatic = NO;// Disable automatic notification for this key. To disable KVO of this class directly, return NO;
    }
    else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}
Copy the code
  1. You need to override the setter method
- (void)setName:(NSString *)name {
    if(name ! = _name) { [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"]; }}Copy the code

But in general it doesn’t feel like it’s necessary to manually trigger KVO because it calls KVO’s response event twice so instead of using those two methods we just add will and did where we need to manually trigger it, okay

The essence of the KVO

KVO is based on the Runtime mechanism

At run time, create an intermediate class based on the original class, which isa subclass of the original class, and dynamically change the current object’s isa pointer to the intermediate class. And overrides the class method to return the class of the original class.

NSLog(@" Class object -%@", object_getClass(self.person));
    NSLog(@" Method implementation -%p"[self.person methodForSelector:@selector(setName:)]);
    NSLog(@" Metaclass object -%@", object_getClass(object_getClass(self.person)));
    
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    
    NSLog(@" Class object -%@", object_getClass(self.person));
    NSLog(@" Method implementation -%p"[self.person methodForSelector:@selector(setName:)]);
    NSLog(@" Metaclass object -%@", object_getClass(object_getClass(self.person)));
Copy the code

We added KVO before and after

  • The set method for the class object and metaclass object to which person points has changed, as has the set method for the property to which the person is listening
  • After adding KVO, ISA in Person points to-NSKVONotifying_PersonClass object
  • After adding KVO,setName:The implementation call is: Foundation_NSSetLongLongValueAndNotifymethods

Isa-swizzling (class pointer exchange) is to take the current isa pointer of an instance object and point it to a newly constructed intermediate class, and do hook methods or other things on the newly constructed intermediate class. This will not affect other instances of the class, but only the current instance.

NSKVONotifying_Person internal implementation

- setName: The primary override method, the notification function that is called when a value is set -class: returns the original classclass-dealloc-_iskVOA to determine if this class has been dynamically subclassed by KVO - (void)setName:(int)name {
}

- (Class)class {
    return [LDPerson class];
}

- (void)dealloc {
    // Finishing up
}

- (BOOL)_isKVOA {
    return YES;
}
Copy the code

How to call methods after ISA mixing

  • Calls the listening property setting method, for exampleSetAge:Is called firstNSKVONotify_PersonThe corresponding method for setting properties
  • Call a non-listening property setting method, such astest, will passNSKVONotify_PersonTo find the Person class object and call the Person Test method

Why override class methods

  • If the class method is not overridden, When this object calls a class method, it’s going to look for that method in its own method cache list, method list, parent class cache, method list all the way up, because a class method is a method in NSObject, and if you don’t override it it might end up returning NSKVONotifying_Person, exposing that class

The implementation of the setter is different

In the screenshot we can see that the implementation of the set method after KVO is called becomes called _NSSetIntValueAndNotify which is a C function that we don’t know what it looks like but we can test it, okay

- (void)setAge:(int)age{
    _age = age;
    NSLog(@"setAge:");
}

- (void)willChangeValueForKey:(NSString *)key{
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey");
}

- (void)didChangeValueForKey:(NSString *)key{
    NSLog(@"didChangeValueForKey - begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey - end");
}
Copy the code

  • First call will
  • And then call the original setAge
  • Finally, the did method is called, notifies the listener that the property value has changed, and the listener executes the observe method

KVO section related issues

  1. What is the essence of KVO?
  • Use Runtime’s API to dynamically generate a subclass and have the instance object’s ISA point to the new subclass
  • When a property of an instance variable object is modified, the _NSSetXXXValueAndNotify function of Foundation is called in the set method of the new subclass
  • willChangeValueForKey
  • Call the original setter
  • DidChangeValueForKey: An internal listener method that triggers the listener
  1. Manually trigger KVO

3. Does changing a member variable directly trigger KVO? KVO 4 will not trigger.We just print the address of the two arrays with the assignment statement and the address of the two arrays is the same, and that’s because we’re only using strong, so it’s a copy of the pointer, and everything is done to the pointer and we set KVO to one of them, we change the value of the array, and then the address of the array changes, because of KVO, Is it the method replaceObjectAtIndex?

This method returns a new array, causing the address of the original array to change, triggering the KVO listening

KVC

What is the KVC

Defined in nsKeyValuecoding.h, is an informal protocol. KVC provides a mechanism for indirectly accessing its property methods or member variables through the == string ==

NSKeyValueCoding provides KVC common access methods, namely the getter method valueForKey and the setter method setValue:forKey, and the derived keyPath method, which are common to each class. And since KVC provides the default implementation, we can also override the corresponding method to change the implementation.

Basic operation

KVC mainly operates on three types: basic data types and constants, object types, and collection types. When using KVC, the attribute name is used as the key and the value is set to assign a value to the attribute

Multiple access

In addition to assigning to the properties of the current object, you can assign to objects deeper into the object. For example, assign a value to the street property of the current object’s address property.

MyAccount setValue:@”qwe” forKeyPath@”address.street”

The ginseng nil

If you pass a nil value to a non-object, KVC will call the setNIlValueForKey method and we can override that method to avoid that

Dealing with non-objects

  • When you setValue, if you want to assign a value to an object of a primitive type, you need to encapsulate the value as an NSNumber or an NSValue
  • When you do valueForKey, you return an object of type id, and the basic data type is also encapsulated as NSNumber or NSValue

ValueForKey automatically encapsulates values into objects, but setValue:forKey: does not.

We have to manually convert the value type to NSNumber/NSValue in order to pass initWithBool:(BOOL)value

KVC The process of obtaining a value

We already have this problem in KVO using the ValueForKey part of the procedure that triggers the KVO listen

Now let’s look at it in detail

setValue:forKey

  • The setter method is used to set the property
  • If a set method is not found, the KVC mechanism will check + (Bool) accessInstanceVariablesDirectly did (direct access to the instance variables) method returns YES (the default return YES)
    • If the override method becomes NO, the -setvalueforundefinedkey call throws an exception :(sets the value for the undefined item)
    • If YES is returned, find the member variable and assign it directly, using _key, _isKey,key,iskey sequence, and throw an exception

Fuck a picture of Tibe from Ireland

valueForKey

  • Search for getKey, key, isKey, _getKey, and _key in sequence. If a method is implemented, the value returned by the method is obtained, and the following methods will not run. If it’s a BOOL or an int or something like that, it wraps it up as an NSNumber object

  • If none of the five methods is available, it will still be accessedaccessInstanceVariablesDirectlyDoes the method return YES?
    • If the override method becomes NO, throw an exception
    • Return YES and find the member variables and value them in the order _key, _isKey, key, isKey

KVC Operation scenarios

Dynamic values and Settings

The use of DYNAMIC values and Settings with KVC is the most basic use

Multivalued operation

KVC can obtain a set of values based on a given set of keys and return them in the form of a dictionary. After obtaining the dictionary, KVC can obtain the value from the dictionary through the key

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
Copy the code

Similarly, batch assignments can be done through KVC. In object call setValuesForKeysWithDictionary: method, can pass in a contains the key, the value of the dictionary in KVC all data can be carried out in accordance with the property names and dictionary of key match, and to give a value to the attribute of the User object assignment.

- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
Copy the code
NSDictionary *dic = @{@"name" : @"book".@"age" : @ "66".@"sex" : @"male"};
    StudentModel *model = [[StudentModel alloc] init];
    
    [model setValuesForKeysWithDictionary:dic];
    NSLog(@ "% @",model);
    NSDictionary *modelDic = [model dictionaryWithValuesForKeys:@[@"name".@"age".@"studentSex"]].NSLog(@"modelDic : %@", modelDic);
Copy the code

If the model attribute does not match dic, you can override the method -(void)setValue:(id)value forUndefinedKey:(NSString *)key

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if([key isEqualToString:@"sex"]) {
        self.studentSex = (NSString*)value; }}Copy the code

Use KVC to access and modify private variables

The essence of KVC is to manipulate a list of methods and find instance variables in memory. We can use this feature to access private variables of a class.

Also if you don’t want to let the outside world use KVC methods access class member variables, can be accessInstanceVariablesDirectly attribute is set to NO

Modify the internal properties of some controls

Many UI controls are composed of internal UI controls, but Apple does not provide an API to access these controls, so we cannot normally access and modify the style of these Spaces. KVC solves this problem in most cases