One: What is KVO?
KVO: The full name is key-value Observing, which is usually called by us. It is a manifestation of the observer mode in OC, and NSNotification is also a kind of observer mode. Every time when an attribute Value of the observed object changes, the registered observer can get notification.
Observer Pattern: The Observer Pattern is used when there is a one-to-many relationship between objects. For example, when an object is modified, dependent objects are automatically notified. The observer model belongs to the behavioral model.
KVO is based on KVC, which monitors changes in the value of a particular key by observing it.
Photo source:KVO summary
Two: how to use
2.1 Basic Usage
KVO uses three steps:
- Add/register KVO listeners
- Implement listening methods to receive notifications of property changes
- Remove KVO listening
2.1.1 Adding an Observer
#import <Foundation/Foundation.h>
@class WYStudent;
NS_ASSUME_NONNULL_BEGIN
@interface WYPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nick;
@property (nonatomic, copy) NSString *downloadProgress;
@property (nonatomic, assign) double writtenData;
@property (nonatomic, assign) double totalData;
@property (nonatomic, strong) NSMutableArray *dateArray;
@property (nonatomic, strong) WYStudent *student;
@end
Copy the code
Add observation
[self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:NULL];
Copy the code
A: Observer is a subscriber to a KVO notification. Subscription must realize observeValueForKeyPath: ofObject: change: context: method 2. KeyPath: description will observe the properties of the relative to the observer. 3. Options: Configure some properties of KVO. There are four options.
NSKeyValueObservingOptionNew: to observe the new value NSKeyValueObservingOptionOld: to observe the old value NSKeyValueObservingOptionInitial: Observing initial values, if you want to be after registered observers, immediately receive a callback, you can join the enumeration values NSKeyValueObservingOptionPrior: respectively before and after the change in value trigger method (i.e., a modified trigger twice)
4. Context, which is passed to the subscribed function to distinguish messages, should be different. (Solved performance and readability issues)
Can look for the same Person in the student age attribute [self. The Person addObserver: self forKeyPath: @ “student. Age” options: NSKeyValueObservingOptionNew context:NULL];
2.1.2 Implementing Listening
Implement observeValueForKeyPath: ofObject: change: context: method
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
Copy the code
2.1.3 Removing the observer
Be sure to remove the observer or some strange bugs will appear.
[self.person removeObserver:self forKeyPath:@"nick" context:NULL];
Copy the code
2.1.4 KVO trigger mode
In 2.1 above, it depends on the automatic triggering of KVO, and the following methods will trigger automatically
Use point syntax using setter methods using KVC’s setValue:forKey: method using KVC’s setValue:forKeyPath: method
So how do you trigger KVO manually? Implement the following method in Person, which returns YES by default and turns off automatic triggering when it returns NO
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
return NO;
}
Copy the code
Next we need to call two key methods manually:
- (void)willChangeValueForKey:(NSString *)key; Called before value changes
- (void)didChangeValueForKey:(NSString *)key; Called after the value changes
Call the above two methods in the Person class set method for manual triggering
- (void)setNick:(NSString *)nick{
[self willChangeValueForKey:@"nick"];
_nick = nick;
[self didChangeValueForKey:@"nick"];
}
Copy the code
2.2 Advanced use
2.2.1 Listening on collection objects
If the changes of collection objects can not be monitored normally in a conventional way, KVO active triggering is mainly dependent onsetter
Method, and the collection add element is none at allsetter
The method.
We can use KVC’s mutableArrayValueForKey: to get the proxy object and operate with it. When the internal object of the proxy object changes, KVO’s listening method is triggered.
- (void)viewDidLoad { [super viewDidLoad]; self.person = [WYPerson new]; self.person.dateArray = [NSMutableArray arrayWithCapacity:1]; / / 🌹 add observation [self. The person addObserver: self forKeyPath: @ "dateArray" options: (NSKeyValueObservingOptionNew) context: NULL]; } - (void)touch began :(NSSet< touch *> *)touches withEvent:(UIEvent *)event{// KVC set array [[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"]; } // 🌹 listener - (void) observeforkeypath :(NSString *)keyPath ofObject:(id)object Change (NSDictionary<NSKeyValueChangeKey, ID > *)change context (void *)context{// fastPath // performance + code readability NSLog(@"%@",change); } // 🌹 remove listener - (void)dealloc{[self.person removeObserver:self forKeyPath:@"dateArray"]; }Copy the code
2.2.2 Key value observation dependency construction
In some cases, a change in one property depends on a change in one or more other properties, that is, when other properties change, the property will also change. DownloadProgress in the Person class, for example, relies on changes to writtenData and totalData.
Add observation
[self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];
Copy the code
In the Person class
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"downloadProgress"]) {
NSArray *affectingKeys = @[@"totalData", @"writtenData"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
Copy the code
2.2.3 Solve the problem that the new and old values will not be triggered
Turn off the automatic trigger and let us trigger it manually.
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
BOOL automatic = YES;
if ([key isEqualToString:@"nick"]) {
automatic = NO;
} else {
automatic = [super automaticallyNotifiesObserversForKey:key];
}
return automatic;
}
- (void)setNick:(NSString *)nick
{
if (![_name isEqualToString:nick]) {
[self willChangeValueForKey:@"nick"];
_name = name;
[self didChangeValueForKey:@"nick"];
}
}
Copy the code
Three: implementation principle of KVO
Apple uses isa-Swizzling to implement KVO. When we call addObserver: forKeyPath: options: context: method, for instance add KVO objects observed after listening, The Runtime API dynamically creates A subclass of instance A, NSKVONotifying_A, and makes instance isa point to this new subclass. Override setter methods for observed properties of class A to notify all observer objects. The isa pointer of this subclass points to its own meta-class object, not the meta-class object of the original class. The IMP corresponding to SEL of rewritten setter method is _NSSetXXXValueAndNotify function in Foundation (XXX is the data type of Key). _NSSetXXXValueAndNotify function will be called when the property of the observed object is changed. This function calls:
willChangeValueForKey:
methods- Original parent class
setter
methods didChangeValueForKey:
Method (internally triggers listeners, which are observed objectsobserver
Monitor method:observeValueForKeyPath:ofObject:change:context:
)
After the KVO listener is removed, the isa of the observed object will be referred back to the original class A, but the NSKVONotifying_A class is not destroyed, it is still saved in memory. For verification, it can be pushed from page A to page B, and the class and subclass will be printed after pop is returned to page A (the printing method is as follows).
- (void)printClasses:(Class) CLS {int count = objc_getClassList(NULL, 0); // Create an array containing the given object NSMutableArray *mArray = [NSMutableArray arrayWithObject: CLS]; Class* classes = (Class*)malloc(sizeof(Class)*count); objc_getClassList(classes, count); for (int i = 0; i<count; i++) { if (cls == class_getSuperclass(classes[i])) { [mArray addObject:classes[i]]; } } free(classes); NSLog(@"WYViewController:classes = %@", mArray); }Copy the code
So, now that we have subclassed NSKVONotifying_A, what methods does it have? Are methods inherited from the parent class, and which methods are overridden?
Use the following method to print
🌹 -ivar-property - (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); }Copy the code
Print result:
**2021-08-19 17:57:36.410107+0800 002-- KVO principle discussion setNickName:-0x180796b78** **2021-08-19 17:57:36.410324+0800 002-- Discussion on KVO principle [83639:3551058] class-0x18079564C ** **2021-08-19 17:57:36.410474+0800 [83639:3551058] Dealloc-0x1807953D0 ** **2021-08-19 17:57:36.410612+0800 002-- KVO principle [83639:3551058] _isKVOA-0x1807953c8**Copy the code
SetNickName,class, dealloc, _isKVOA, setNickName,class, dealloc, _isKVOA
We create a subclass of WYPerson class WYTeacher, and use printClassAllMethod to print all WYTeacher methods. But we find that we don’t print any methods. The inherited methods are in the metaclass. Then setNickName,class, dealloc, _isKVOA must be overwritten.
class
:class
Method that returns the parent classclass
Object, the purpose is to keep the outside world from knowingKVO
The existence of dynamically generated classes;dealloc
Release:KVO
Something produced in the process of use;_isKVOA
: used to indicate that it is aKVO
In the class.
Specific code exploration process we can refer to this code to explore the implementation principle of KVC/KVO is very detailed.
Four: FBKVOController
4.1 Disadvantages of system KVO
In the process of using KVO, we can feel the unfriendly system KVO more or less:
-
Add/register KVO listener, implement listener method to receive notification of property change, and remove KVO listener.
-
The observer needs to be removed manually, at the right time, and not repeatedly.
-
The code that registers the observer is different from the code context in which the event occurs. The context is passed through a void * pointer.
-
Need to implement – observeValueForKeyPath: ofObject: change: context: method, more troublesome.
-
In complex business logic, it is relatively troublesome to accurately judge the observed. When there are multiple observed objects and attributes, a lot of IF needs to be written in the method for judgment.
4.2 Advantages of FBKVOController
FBKVOController is a Facebook open source framework based on the system KVO implementation. Support for Objective-C and Swift.
thenFBKVOController
What are the advantages?
-
It automatically removes the observer, so we don’t have to remove it every time;
-
Functional programming, can be a line of code system KVO three steps;
-
The implementation of KVO is the same as the code context where the event occurs and does not require parameters to be passed across methods;
-
Added support for processing NSKeyValueObserving callbacks by custom block and SEL operations.
-
Each keyPath corresponds to a block or SEL. You do not need to use if to determine the keyPath.
-
Multiple properties of an object can be monitored at the same time, simple writing;
-
Thread safety.
4.3 Use of FBKVOController
/ / 🌹 instance creation FBKVOController * KVOController = [FBKVOController controllerWithObserver: self]. self.KVOController = KVOController; // 🌹 observe // use block [self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) { clockView.date = change[NSKeyValueChangeNewKey]; }]; // use SEL [self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew action:@selector(wy_observerAge:)]; SEL - (void) wy_observerAge {NSLog(@" I'm here "); }Copy the code
4.4 FBKVOController principle
How to use KVO Elegantlyios – FBKVOController
IOS: KVO, KVO, KVC, KVO, KVO