Series of articles:OC Basic principle series.OC Basic knowledge series

In the last article we introduced KVC, where there are portals. Today’s article is about KVO associated with KVC.

KVO met

KVO definition

KVO: the full name is key-value observing, which is what we are familiar with. KVO is a monitoring mechanism, which informs the observer of the change of the property of the specified object observed.

KVO is closely related to KVC, as can be seen from the official document portal of KVO, because KVO listens for changes in the attribute value, which is assigned with KVC.

KVO and inform

As mentioned above, KVO monitors the change of attribute value and can respond quickly when the attribute value changes, telling the observer. This is somewhat similar to NSNotification, so what are the similarities and differences between the two?

  • The same
    • 1. Both are observer mode, both are listening
    • 2. It’s all one to many
  • different
    • 1.KVO is only used to listen for attribute changes and can only be looked up by NSString. The compiler does not check NSString for errors.
    • 2.NSNotification sending messages can actually be controlled by itself, while KVO will be called automatically whenever the attribute changes.

KVO usage process

KVO basic usage process

There are three main steps to using KVO

  • 1. Register the observer (the context argument is an object, so use NULL because NULL is an object)
[self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:NULL];
Copy the code
  • 2. Implement the KVO callback
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if ([keyPath isEqualToString:@"nick"]) { NSLog(@"%@",change); }}Copy the code
  • 3. Remove the observer
[self.person removeObserver:self forKeyPath:@"nick" context:NULL];
Copy the code

The context of use

The official documentation describes context as follows:

Context is used to distinguish objects with the same name. In the KVC callback method, context can be used to distinguish objects. The benefits are convenience, readable code, better performance and more security.

An example of using context to distinguish

static void *PersonNameContext = &PersonNameContext; static void *StudentNameContext = &StudentNameContext; [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL]; [self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL]; // KVO callback - (void) observeforkeypath :(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if (context == PersonNameContext) { NSLog(@"%@",change); }else if (context == StudentNameContext) { NSLog(@"%@",change); }}Copy the code

The necessity of KVO removal

The official documentation explains the removal of KVO:A brief explanation of the official documentation:

  • 1.AddObserver and removeObserver must have a one-to-one correspondenceIf an unregistered observer is removed, an NSRangeException is thrown.
  • 2. When the observation is over,Observers do not remove themselvesIf theConditional triggering continues to send notifications regardless of the observer status (whether or not the observer is destroyed) so that memory access exceptions occur if the observer is destroyed.
  • 3. The protocol does not provide a method to ask whether the object is the observer or the observed, so to avoid errors, toRegisters an observer as an observer during initializationAnd, inDeregister during release(Usually in the dealloc method)

To summarize, there must be a one-to-one correspondence between observer registration and removal, otherwise an error will be raised. Verify: Prepare the code

*************LGViewController*************
@implementation LGViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.student = [LGStudent shareInstance];
    [self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
}
- (void)dealloc {
}
@end

*************LGDetailViewController*************
@implementation LGDetailViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.student = [LGStudent shareInstance];
    [self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
}

- (void)dealloc{
    [self.student removeObserver:self forKeyPath:@"name"];
}
@end
Copy the code

So if I run this code, I’m going to go to LGViewController and trigger the listener, and then I’m going to push to LGDetailViewController and trigger the listener, and then I’m going to exit LGDetailViewController, exit LGViewController, Then go to the LGViewController page again, click trigger listener, will report the following error.

The reason: Because for the first time after registered KVO observer isn’t gone, again into the interface, lead to the second registration KVO observer, lead to KVO observations duplicate registration, and a notification object for the first time in memory, were not released, receives the property value changes at this time of notice, there will be can’t find the original notification object, and can only find existing notification object, That is, the observer registered for the second KVO, thus causing a wild pointer – like crash

KVO automatic switch and manual switch

KVO observation can be enabled in two ways: automatic and manual

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
	return YES;
}
Copy the code

The above method is: whether to automatically enable KVO observer mode, YES means automatically enable, NO means disabled, you need to manually enable. The system is enabled by default

When the above method returns NO, that is, the KVO listening trigger needs to be triggered manually, use the following method to enable listening

- (void)setNick:(NSString *)nick{
    [self willChangeValueForKey:@"nick"];
    _nick = nick;
    [self didChangeValueForKey:@"nick"];
}
Copy the code

KVO listens for one-to-many

One-to-many in KVO observer, which means to listen for multiple property changes by registering a KVO observer.

Let’s take download progress as an example. Total download size is totalData, current download writtenData. Use KVO to listen for the current downloadProgress. Monitor totalData and writtenData respectively, this is not, the main said is by implementing keyPathsForValuesAffectingValueForKey method, merge the two observation object as an observation object

/ / need the place of registration of [the self. The person addObserver: self forKeyPath: @ "downloadProgress options: (NSKeyValueObservingOptionNew)" context:NULL]; // (void)touch began :(NSSet< uittouch *> *) view withEvent:(UIEvent *)event{self.person.currentdata += 10; self.person.totalData += 1; } //4, removeObserver - (void)dealloc{[self.person removeObserver:self forKeyPath:@"currentProcess"]; } / / implementation keyPathsForValuesAffectingValueForKey method + (NSSet < > nsstrings * *) keyPathsForValuesAffectingValueForKey: (nsstrings *)key{ NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([key isEqualToString:@"downloadProgress"]) { NSArray *affectingKeys = @[@"totalData", @"writtenData"]; keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys]; } return keyPaths; }Copy the code

KVO looks at mutable arrays

KVO observation is based on the assignment of KVC,If you add an array to a mutable array directly, the setter method is not called and KVO is not triggered, that is, adding an element to an array through the [self.person.datearray addObject:@"1"] method is not a KVO notification callback. So how do you trigger a callback? As mentioned above, KVO callbacks are based on KVC assignments, so we can check the official KVC documentation for mutable array handlingYou know how to add elements to a mutable array using KVC, so let’s listen for a mutable array, okay

// Register the mutable array KVO observer self.person.dateArray = [NSMutableArray arrayWithCapacity:1]; [self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL]; // KVO callback - (void) observeforkeypath :(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%@",change); } // touch began :(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"]; } // removeObserver - (void)dealloc{[self.person removeObserver:self forKeyPath:@"dateArray"]; }Copy the code

The listener callback prints the following :(we see the element added to the mutable array)We see that the printed field is kind, which represents the key change type and is an enumerated value. There are four main typesDifferent kinds of assignment listeners are differentWe see that the assignment of kind to name is assignment, and the addition of kind to array is insertion

After introducing the use of KVO above, let’s study the bottom layer of KVO

The underlying principles of KVO

Analysis of the document

First of all, let’s open the official documentation for the introduction of KVOWhat the document says:

  • 1.KVO is implemented using an ISa-Swizzling technique
  • 2. Isa Pointers, as the name implies, are classes that point to objects that maintain an allocation table, which essentially contains Pointers to methods and other data that the class implements.
  • 3. When an observer registers an object’s properties, the observed isa pointer 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.
  • 4. So you should not rely on isa Pointers to determine membership. Instead, you should use class methods to determine the class of instance objects

As we know from the above documentation, KVO is implemented through ISa-Swizzling, and intermediate classes are generated when object properties are registered and isa Pointers are pointed to them.

Code validation

1.KVO observation of attributes

In LGPerson there is a property nickName and a member variable name. Register KVO observations for each of them. What happens when the property changes

  • Prepare code to listen for name and nickName

  • Here we use gesture clicks to trigger KVO

  • Print chang in the callback

We run the code, then click on the screen and it prints:

Call (‘ nickName ‘, ‘nickName’, ‘nickName’, ‘nickName’, ‘nickName’)

Conclusion: KVO only observes attributes, not member variables, and the difference between member variables and attributes is that attributes have setter methods, while member variables do not. KVO is looking at setter methods to send notifications.

2. The middle class

As stated in the official documentation above, if an object property is registered, an intermediate class is generated and an ISA pointer is pointed to the intermediate class. So let’s see what this middle class is

The ISA pointer to the instance object Person points to the LGPerson class before viewing the properties

After the observer is registered, the ISA pointer to the instance object Person points to NSKVONotifying_LGPerson

Conclusion: The middle class is NSKVONotifying_, and the middle class NSKVONotifying_LGPerson will be generated after KVO is registered. It also points the isa pointer to the intermediate class. So let’s see if NSKVONotifying_LGPerson is a subclass

2.1 Determine whether the intermediate class is a subclass

Ideas:To determine whether an intermediate class is a subclass, we can look at all the subclasses of the class and see if there is an intermediate class that we want to determine, if there is, if there is not, it is not. Prepare the code: Before KVO registration, we look at the prints once, and after KVO registration, we look at the prints again to see the changes. So let’s run itAfter KVO is registered, the subclass of print is NSKVONotifying_LGPersonNSKVONotifying_LGPerson is a subclass of LGPerson

2.2 What methods do intermediate classes have

What are the methods in the middle class?I must be using methodList, so I'm going to go through methodList and print out the methods that I've saved. Prepare the code:We call printClassAllMethod after KVO registration,Value passed: objc_getClass("NSKVONotifying_LGPerson"). So let’s run the codeNSKVONotifying_LGPerson: setNickName, class, dealloc, _isKVOA

  • Rewrite the setNickName method in LGStudent and get all the LGPerson methods

The above method shows thatOnly methods overridden by subclasses are printed in the subclass's list of methods, not inherited ones. So with that in mind let’s take a look at all the methods of our LGPerson classPerson: NSKVONotifying_LGPerson: NSKVONotifying_LGPerson

  • 1.The NSKVONotifying_LGPerson middle class overrides the setName method of parent LGPerson
  • 2.The NSKVONotifying_LGPerson middle class overrides the NSObject class, dealloc, and _isKVOA methods (where dealloc is the release method and _isKVOA is the method to determine whether the current class isKVO).

2.3 Who is isa pointing to when the observer is removed from dealloc, and will the intermediate class be destroyed?

After KVO is registered, the isa of person points to NSKVONotifying_LGPerson. Let’s see if the ISA pointer changes when money is destroyedWe see that inBefore KVO is destroyed, the ISA pointer to Person still points to NSKVONotifying_LGPerson. Let’s see if it changes after it’s destroyedConclusion: From the print comparison above, we can see that,Before KVO was removed, the isa pointer to person was always pointing to NSKVONotifying_LGPerson, but after KVO is removed, the ISA pointer to Person will be pointing back to LGPerson.

2.4 Does the middle class NSKVONotifying_LGPerson still exist after KVO is removed?

We iterate on the last page of the KVO registration page to find all subclasses of LGPerson (If SKVONotifying_LGPerson is destroyed, then the subclass SKVONotifying_LGPerson does not exist). Prepare the code:We trigger the method that requests all subclasses by clicking on the screen. , run the codeNSKVONotifying_LGPerson, KVO destroyed, NSKVONotifying_LGPerson was not removed from memory. The reason:Once the intermediate is registered in memory, it is not destroyed due to reuse issues (KVO registers again).

conclusion

To sum up, the intermediate class is explained as follows:

  • After the instance object registers the KVO observer, the ISA pointer is changed from the original class to point to the intermediate class
  • The middle class overrides setter methods, class, dealloc, _isKVOA methods for observing properties
  • In the dealloc method, the instance object ISA points to the original class instead of the intermediate class after the KVO observer is removed
  • Intermediate classes are stored in memory from the moment they are created and will not be destroyed

Wrote last

This article mainly describes the definition of KVO, usage, and the intermediate process of monitoring. Some problems about intermediate class are also analyzed. With a more comprehensive view of KVO, in the next article we will customize a KVO to implement listening for attributes.