1. Know before you explore
1.1 What is KVO?
Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other (Key-value observation is a mechanism that allows an object to be notified when a specified property of another object changes.)
Important: In order to understand key-value observing, you must first understand key-value coding.
Attached: the address of the previous article about KVC exploration juejin.cn/post/684490… .
Ii. Preliminary Study of KVO (Common Usage scenarios of KVO)
2.1 Functions of parameters in common Functions
self.person = [LGPerson new];
self.student = [LGStudent shareInstance];
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
[self.person addObserver:self forKeyPath:@"nick" options:(NSKeyValueObservingOptionNew) context:NULL];
[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
Copy the code
In previous development, we used to pass nil to context, or NULL.(Except in special cases, it’s best to pass NULL because context is void *). But in a more complicated situation, what does context do? Let’s take a look at the Apple development documentation, which was also introduced in the previous article, so we won’t do the introduction here and get straight to the point.
context
The context pointer in the addObserver:forKeyPath:options:context:
message contains arbitrary data that will be passed back to the observer in the corresponding change notifications. You may specify NULL
and rely entirely on the key path string to determine the origin of a change notification, but this approach may cause problems for an object whose superclass is also observing the same key path for different reasons.
A safer and more extensible approach is to use the context to ensure notifications you receive are destined for your observer and not a superclass.
The address of a uniquely named static variable within your class makes a good context. Contexts chosen in a similar manner in the super- or subclass will be unlikely to overlap. You may choose a single context for the entire class and rely on the key path string in the notification message to determine what changed. Alternatively, you may create a distinct context for each observed key path, which bypasses the need for string comparisons entirely, resulting in more efficient notification parsing. Listing 1 shows example contexts for the balance
and interestRate
properties chosen this way.
To get a sense of what it means: You can specify a NULL string and totally dependent on the key path to determine the source of change notification, but this method may cause the object appear problem, the object of the superclass due to different reasons in observing the same key path, a more secure, more scalable approach is to use the context to ensure that the received notification is sent to the observer, Not a superclass.
Context is better distinguished when both subclasses and superclasses observe a property in the class. Look at the code for example:
LGStudent is a subclass of LGPerson with the same property name. When a property changes, the current ViewController gets the corresponding callback:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"LGViewController - %@",change);
}
Copy the code
If Context passes NULL as before:
if (object == self.student) {
if ([keyPath isEqualToString:@"name"]) {}else if ([keyPath isEqualToString:@"nick"]) {}}else if (object == self.person){
if ([keyPath isEqualToString:@"name"]) {}else if ([keyPath isEqualToString:@"nick"]) {}}Copy the code
If the context is passed in the code, the code looks like this:
static void *PersonNickContext = &PersonNickContext;
static void *PersonNameContext = &PersonNameContext;
static void *StundentNameContext = &StundentNameContext;
Copy the code
// OC -> c superset [self.person addObserver:selfforKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:PersonNameContext];
[self.person addObserver:self forKeyPath:@"nick" options:(NSKeyValueObservingOptionNew) context:StundentNameContext];
[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
Copy the code
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if (context == PersonNickContext) {
}else if (context == PersonNameContext){
}else if (context == StundentNameContext){
}
NSLog(@"LGViewController - %@",change);
}
Copy the code
Whether we can use context in the proxy to determine the property changes of the corresponding object is a safer and more extensible method.
2.2 Remove properties currently being viewed by the object at page destruction time (dealloc)
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"name"];
[self.person removeObserver:self forKeyPath:@"nick"];
[self.student removeObserver:self forKeyPath:@"name"];
// [self.timer invalidate];
// self.timer = nil;
}
Copy the code
Why remove the observer?
- To receive removeObserver: forKeyPath: context: after the message, Observation object will no longer receive any observeValueForKeyPath: ofObject: change: context: messages (used for the specified key path and object).
- If it is not already registered as an observer, requesting it to be removed as an observer causes a crash.
- Observers do not automatically remove themselves when unassigned. The observed object continues to send notifications, ignoring the status of the observer. However, as with any other message, a change notification sent to a freed object triggers a memory access exception. Therefore, you can ensure that the observer deletes itself before it disappears from memory.
- The protocol does not provide a way to ask whether an object is an observer or an observed. Construct code to avoid release-related errors. The typical pattern is to register as an observer during observer initialization (such as in It or viewDidLoad), unregister the observer during release (usually in Dealloc), ensure proper pairing and orderly adding and removing messages, and unregister the observer before it is freed from memory.
2.3 “Automatic Shift” and “Manual shift”
What are “manual transmission” and “automatic transmission “?
According to apple’s development documentation: Manual change notification provides additional control over when notifications are emitted, and requires additional coding. You can control automatic notifications for properties of your subclass by implementing The class method automaticallyNotifiesObserversForKey. (manual change notification provides additional control of when issued a circular, and the need for additional coding. By implementing a class method automaticallyNotifiesObserversForKey:, can control the attributes of the subclass automatic notification)
NSObject provides a basic implementation of automatic key-value change notification. Automatic key-value change notification informs observers of changes made using key-value compliant accessors, as well as the key-value coding methods. Automatic notification is also supported by the collection proxy objects Returned by, for example, mutableArrayValueForKey.(NSObject provides a basic implementation for automatic notification of key value changes. Automatic key-value change notification notifies observers of changes made using key-value compatible accessors and the key-value encoding method. The collection proxy object returned by mutableArrayValueForKey also supports automatic notification. The example shown in Listing 1 causes any observer of the property name to be notified of the change.)
How do you trigger manual and automatic?
Automatic transmission:
// Call the accessor method.
[account setName:@"Savings"];
// Use setValue:forKey:.
[account setValue:@"Savings" forKey:@"name"];
// Use a key path, where 'account' is a kvc-compliant property of 'document'.
[document setValue:@"Savings" forKeyPath:@"account.name"];
// Use mutableArrayValueForKey: to retrieve a relationship proxy object.
Transaction *newTransaction = <#Create a new transaction for the account#>;
NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
[transactions addObject:newTransaction];
Copy the code
Does that look familiar? That’s right, the basic call to KVC. It turns out that KVC calls automatically trigger key-value observation (KVO). So to understand key-value observation, you must first understand key-value coding.
Manual shift:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
Copy the code
To implement manual observer notification, call willChangeValueForKey before changing the value and didChangeValueForKey after changing the value.
- (void)setBalance:(double)theBalance {
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
Copy the code
Okay, okay, let’s check this out:
ViewController:
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:PersonNameContext];
[self.person addObserver:self forKeyPath:@"nick"options:(NSKeyValueObservingOptionNew) context:StundentNameContext]; // 1: context -- multiple objects -- same keypath // more convenient -- more secure -- direct [self.student addObserver:selfforKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
Copy the code
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if (context == PersonNickContext) {
}else if (context == PersonNameContext){
}else if (context == StundentNameContext){
}
NSLog(@"LGViewController - %@",change);
}
Copy the code
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.name = @"null";
[self.person setValue:@"xiang" forKey:@"nick"];
self.student.name = @Senhai Beijing Language and Culture University; // // KVO built on KVC // [[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"2"];
}
Copy the code
LGPerson:
/ / automatic switch + (BOOL) automaticallyNotifiesObserversForKey: (nsstrings *) key {return YES;
}
Copy the code
Let’s look at the results:
And then:
/ / automatic switch + (BOOL) automaticallyNotifiesObserversForKey: (nsstrings *) key {return NO;
}
Copy the code
Take a look at the results:
Didn’t print anything? Explain automaticallyNotifiesObserversForKey automatic notification switch can control the subclass attribute.
But what if you turn off the autoswitch and still want to be notified when a specified property changes?
/ / automatic switch + (BOOL) automaticallyNotifiesObserversForKey: (nsstrings *) key {if ([key isEqualToString:@"name"]||[key isEqualToString:@"nick"]) {
return YES;
}
return NO;
}
Copy the code
We can evaluate the Key as shown above, with another method (call willChangeValueForKey before changing the value and didChangeValueForKey after changing the value) :
/ / automatic switch + (BOOL) automaticallyNotifiesObserversForKey: (nsstrings *) key {return NO;
}
- (NSString *)downloadProgress{
if (self.writtenData == 0) {
self.writtenData = 10;
}
if (self.totalData == 0) {
self.totalData = 100;
}
return [[NSString alloc] initWithFormat:@"%f", 1.0 f * self. WrittenData/self. TotalData]; } - (void)setNick:(NSString *)nick{
[self willChangeValueForKey:@"nick"];
_nick = nick;
[self didChangeValueForKey:@"nick"];
}
Copy the code
3.KVO advanced
Let’s take a look at apple’s development documentation:
Key-Value Observing Implementation Details
Automatic key-value observing is implemented using a technique called isa-swizzling.
The isa
pointer, as the name suggests, points to the object’s class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true 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 the isa
pointer to determine class membership. Instead, you should use the class
method to determine the class of an object instance.
Automatic key observation is implemented using a technique called Isa Swizzing. As the name implies, the ISA pointer points to the object class that maintains the dispatch table. The dispatch table essentially contains Pointers to methods implemented by the class, as well as other data. When an observer registers an object’s properties, the observer’s ISA pointer is modified to point to an intermediate class instead of the true class. Therefore, the value of the ISA pointer does not necessarily reflect the actual class of the instance. You must never rely on isa Pointers to determine class membership. Instead, you should use class methods to determine the class of an object instance.
Many friends here don’t quite understand, what’s not? What is this? What is Isa Swizzing, and what is this intermediate class? No more words, just the code.
3.1 Isa Swizzing and intermediate Classes
LGPerson class:
@interface LGPerson : NSObject{
@public
NSString *name;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
- (void)sayLove;
@end
Copy the code
#import "LGPerson.h"
@implementation LGPerson
- (void)setNickName:(NSString *)nickName{
_nickName = nickName;
}
- (void)sayHello{
}
- (void)sayLove{
}
@end
Copy the code
LGStudent:
#import "LGStudent.h"
@implementation LGStudent
- (void)sayHello{
}
@end
Copy the code
#import "LGStudent.h"
@implementation LGStudent
- (void)sayHello{
}
@end
Copy the code
Okay, so we’re ready to do our homework, so create two classes, LGPerson and LGStudent.
And then create a LGViewController, which jumps into the ViewController created by the engineering system.
LGViewController :
– (void)printClasses:(Class) CLS {} is a method for iterating through classes and subclasses
– (void)printClassAllMethod:(Class) CLS {} -ivar-property
Let’s make a breakpoint at line 53 and run the code:
The classes and subclasses that iterate over LGPerson are LGPerson and LGStudent. It also prints the methods in lgPerson.h.
Then we use LLDB command to print:
Is that okay? Next up55 lineBreak point again:
And then we can use it againLLDBCommand print:
Omg NSKVONotifying_LGPerson!! Now let’s look at what we just mentioned:
When an observer registers an object’s properties, the observer’s ISA pointer is modified to point to an intermediate class instead of the true class. Therefore, the value of the ISA pointer does not necessarily reflect the actual class of the instance. You must never rely on isa Pointers to determine class membership.
NSKVONotifying_LGPerson ISA derived class that the system created for us with the prefix NSKVONotifying_. The ISA of self.person refers to this derived class.
So what does this derived class have to do with LGPerson? Let’s hit the breakpoint at line 56 and see the output:
This derived class is a subclass of LGPerson.
3.2 What does NSKVONotifying_LGPerson do?
Why does Apple create a derived class when it registers an object property? What does that class do? In the class structure, the longest used is the data in the bits, which contains the instance method, the instance variable, and so on. Methods and variables are the ones we use most often. Let’s break on line 59 to see the method in a derived subclass of NSKVONotifying_LGPerson:
Dynamic subclasses override setNickName (setter), class, dealloc, _isKVOA, etc.
Why did I override setter methods? Let’s think about it… KVO overwrites the setter method to modify the nickName.
And why rewrite itclassMethod? Remember this picture?
Object’s ISA pointer was modified to point to an intermediate class instead of the true class, so Po class_getName([self.person class]) should return NSKVONotifying_LGPerson, The NSKVONotifying_LGPerson class overrides the class method and returns its parent LGPerson.
As for dealloc and _isKVOA, these two methods will be discussed later in the KVO customization.
3.3 When does the object ISA refer back?
When an observer registers an object’s property and the observer’s ISA pointer is modified to point to an intermediate class instead of the true class, when does the observer’s ISA pointer point back? Does it always point to this middle class?
In fact, if you think about it, you can get the answer. The isa pointer is modified because the observer registers an object’s properties.
So when I’m not observing (not removing), I’m pointing back.
Po object_getClassName(self.person) returns to LGPerson.
3.4 Will the NSKVONotifying_LGPerson subclass be destroyed?
Let’s go from LGViewController back to ViewController and iterate through LGPerson and subclasses:
Look at the console:
Does the derived subclass NSKVONotifying_LGPerson still exist? The next time an observer registers an object’s properties, it will have to create a new derived class.
Iv. Summary of KVO
1. When A KVO is observed on object A, A subclass is dynamically generated, and the isa of the object is pointed to the newly generated subclass
2. KVO is essentially a setter method for listening properties. As long as the object being observed has member variables and corresponding set methods, it can be observed through KVO
Subclasses override the set, class, dealloc, _isKVOA methods of the parent class
4. After all listeners are removed from the observed object, the isa of the observed object is pointed to the original class
5. When all the listening of the observed object is removed, the dynamically generated class will not be deregister, but will be used in the next observation to avoid repeatedly creating intermediate subclasses