I. Overview of KVO
KVO (key-value Observing) is a mechanism that allows you to register as an observer of other objects. When an attribute Value of the object being observed changes, the registered observer can be notified.
2. Basic use of KVO
The following steps must be performed in order for an object to receive a key value for a KVO compatible property notification:
- To register an observer with the observed object, use this method:
addObserver:forKeyPath:options:context:
.- implementation
observeValueForKeyPath:ofObject:change:context:
To receive notification messages about changes in values within the observer.- Called before the observer is freed from memory
removeObserver:forKeyPath:
To remove the observer.
- 1. Register observers
- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context;
Copy the code
- 2. Register observers
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context;
Copy the code
- 3. Unregister the observer
- (void)removeObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath;
Copy the code
1. Basic use
First, we have a Person class with only a name attribute, which looks like this:
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
@end
Copy the code
ViewController. M:
@interface ViewController () @property (nonatomic, strong) Person *person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _person = [Person alloc]; / / register the self, that is, the controller for his observers [_person addObserver: self forKeyPath: @ "name" options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; } - (void)touchesBegan:(NSSet< touch *> *)touches withEvent:(UIEvent *)event{_person.name = @@view; } // response method - (void) observeforkeypath :(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%@ - %@ - %@",keyPath,object,change); } // removeObserver - (void)dealloc{[_person removeObserver:self forKeyPath:@"name"]; } @endCopy the code
Click on the screen and console output:
2021-01-18 15:59:15.571749+0800 KVO[3322:245856] name - <Person: 0x6000008c0350> - {kind = 1; new = "\U54c8\U54c8"; old = "<null>"; }Copy the code
NSKeyValueChange value:
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {NSKeyValueChangeSetting = 1, / / a value NSKeyValueChangeInsertion = 2, / / insert NSKeyValueChangeRemoval = 3, / / removed NSKeyValueChangeReplacement = 4, / / replace};Copy the code
2. Manually trigger the Observer callback
When we change the value of name, the Observer callback is automatically triggered, but sometimes we don’t always want to notify each time, such as when a condition is met. Let’s start by adding a method in Person to turn off automatic triggering:
/ / for all of them are closed / / + (BOOL) automaticallyNotifiesObserversOfName {/ / return NO; / / / /} can be closed + (BOOL) to a specified key automaticallyNotifiesObserversForKey: (nsstrings *) key {if ([key isEqualToString: @ "name"]) { NSLog(@" auto trigger off "); return NO; } return YES; }Copy the code
Add manually triggered code to the person.name assignment:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{// the value of name is about to change [_person willChangeValueForKey:@"name"]; _person.name = @person. name; [_person didChangeValueForKey:@"name"]; }Copy the code
3. Observe attributes. Attribute change
The Person class has the Dog attribute, and the Dog class has the age attribute. Observe the change of age as follows:
[_person addObserver:self forKeyPath:@"dog.age" options:(NSKeyValueObservingOptionNew) context:nil];
Copy the code
4. Observe multiple attribute changes
Add firstName and lastName to the Person class. Add a listener to name and receive notification when either firstName or lastName changes:
- in
Person
Add class methods to a class+ keyPathsForValuesAffectingValueForKey
, returns a container. - Registered to observe
Person
的name
Property, so that you canfirstName
和lastName
When changes, the corresponding callback is also received.
+ (NSSet<NSString *> *)keyPathsForValuesAffectingName {
NSSet *keyPaths = [NSSet setWithArray:@[@"firstName",@"lastName"]];
return keyPaths;
}
Copy the code
4. Observe mutable array changes
Person adds a mutable array property. When adding data to this mutable array, setter methods are not called and KVO callbacks are not triggered, as follows:
- (void)viewDidLoad { [super viewDidLoad]; _person = [Person alloc]; // sons is a mutable array _person.sons = [@[] mutableCopy]; / / then observe the changes of sons [_person addObserver: self forKeyPath: @ "sons" options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {// scenes created [_person.sons] addObject:@"lili"]; }Copy the code
[_person.sons addObject:@”lili”]; The KVO callback cannot be triggered. For the collection type of a mutable array, the KVO callback must be triggered by adding elements to the mutable array using the mutableArrayValueForKey method.
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [[_person mutableArrayValueForKey:@"sons"] addObject:@"lili"]; }Copy the code
Third, KVO low-level exploration
KVO uses isa-Swizzling technology for automatic key observation.
- The ISA pointer, which points to the class of the object, holds a schedule. The dispatch table essentially contains Pointers to the methods implemented by the class, as well as other data.
- When an observer is registered for an object’s properties, the ISA pointer to the observed object is modified to point to the intermediate class instead of the real 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 isa Pointers to determine class members. Instead, you should use this
[ class]
Method to determine the class of an object instance.
1. Verify that KVO only observes attributes
- Create a Project at random, create a class that has one property and one member variable.
- Instantiate an object of a class and add attributes and member variables to the observer
ViewController
。 - add
touchBegin
Method to change both properties and instance variables by clicking on the screen. KVO
To observe is to havesetter
Method variables, which can be member variables, but must be usedKVC
Properties can be directly assigned for observation.
(Note: specific verification is self-implemented)
2. Verify the KVO intermediate class
According to the official documentation, after registering a KVO observer, the isa pointer to the observed object changes to point to an intermediate class.
Comparison of class names before and after registration:
Get the parent of the NSKVONotifying_Person classPerson
.NSKVONotifying_Person
是 Person
The subclass:
We can also get the class information with the following code:
- (void)printClassAllMethod:(Class)cls{ unsigned int count = 0; Method *methodList = class_copyMethodList(cls, &count); for (int i = 0; i<count; i++) { Method method = methodList[I]; SEL sel = method_getName(method); IMP imp = class_getMethodImplementation(cls, sel); NSLog(@"%@-%p",NSStringFromSelector(sel),imp); } free(methodList); } ///// call [self printClassAllMethod:objc_getClass("NSKVONotifying_Person")];Copy the code
To:
2021-01-18 20:47:41.791402+0800 KVO[4396:407755] setNickName: -0x10918C54b 2021-01-18 20:47:41.791516+0800 KVO[4396:407755] class-0x10918AFD5 2021-01-18 20:47:41.791612+0800 KVO[4396:407755] dealloc-0x10918AD3a 2021-01-18 20:47:41. 791706 + 0800 KVO _isKVOA x10918ad32 0 [4396-407755]Copy the code
The NSKVONotifying_Person class has four methods:
setXxx
: overrides the property being observedset
Methods.class
: Rewrite your ownclass
Methods.dealloc
: Rewrite your owndealloc
Methods._isKVOA
: Judge whether or notKVO
The generated intermediate class.
NSKVONotifying_xxx is the object whose ISA points to the class NSKVONotifying_xxx after the KVO observation. The set method of NSKVONotifying_xxx is used to change the property.
The middle subclass NSKVONotifying_xxx overrides the setter, dealloc, class methods of the parent class. In addition, when the observer logs out, the intermediate class will not be destroyed, but cached, when necessary to call directly, reducing the cost of adding observers next time.
Reference documentation
-
Key-value Observing Programming Guide This document is an official Apple document
-
[iOS] KVO Basic Principles code_ce