Haven’t written for a long time, take I haven’t written KVO start ~ ~
Have a working pit of small friends, private message oh ~ ~
Interviews are so hard
1. What is KVO?
The official documentation
Introduction to the
KVO is the full name of key-value Observing, which can be used to monitor the change of an object’s property Value and adjust the observant’s observeforkeypath: ofObject: change:context: method.
2. Basic use of KVO
Trilogy:
-
Registered KVO
observer
Observer:keyPath
: Observation attributeoptions
: enumeration checks whether newValue or oldValuecontext
- This parameter is used when the parameter is null
NULL
- This parameter is used when the parameter is null
[self.person1 addObserver:self forKeyPath:@"age" options:options context:NULL]
-
Dealloc removing KVO
[self.person1 removeObserver:self forKeyPath:@"age"];
-
Write the KVO callback function.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {NSLog(@" listening to the %@ property value changed - %@ - %@", object, keyPath, change, context); }Copy the code
3. Problems existing in KVO
@interface Person : NSObject @property(assign, nonatomic) int age; @end @implementation Person @end self.person1 = [[Person alloc] init]; self.person1.age = 1; self.person2 = [[Person alloc] init]; self.person2.age = 2; // Add observer to person1 object, Observe attribute age NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person1 addObserver:self forKeyPath:@"age" options:options context:NULL];Copy the code
As in the code above, the same class generates a different object, and changes to the age of the Person1 object can be traced to the callback method. Changes to the age of the Peron2 object do not. Same class, same method call, why? So this requires us to study their use.
4. Principle analysis of KVO
After debugging the screenshot above, it is found that the ISA of the person1 object to which the observer is added has changed. I changed it to NSKVONotifying_Person, so IT no longer points to the Person object.
This class is an intermediate class generated through the Runtime API that inherits our Person class and overrides the setter methods for the observed property.
So this is the graph from above
Essentially, something like self.person1.age = 11; self.person2.age = 13; Both are setter methods that call objects. Calling the person1 object sends a notification; calling the person2 object does not.
Since the instance object’s method exists in its class object, send the setAge method, first through ISA to find its class object, looking for the method. Here we find the NSKVONotifying_Person class. I found a way to rewrite it. That is, calling the setter method of the intermediate class.
The setter method that the Person2 object calls. Because its ISA is still a Person object, the call is still the Setter method in the Person class.
It can be seen from the official documents that the realization of Automatic key-value observing uses ISa-Swizzling technology
⚠️ _NSSetIntValueAndNotify This method is not fixed _NSSet [XXXXXX] ValueAndNotify This method is not fixed.
Such as int the age to double the age, the will is _NSSetDoubleValueAndNotify accordingly
4.1 KVO
The generatedThe middle class
Why rewriteclass
Methods?
As mentioned above, in addition to rewriting our setter methods, the new generated class also includes _isKVOA and rewriting dealloc and class methods.
Methods like _isKVOA return true to indicate KVO, and deallco do some cleanup. Why should class be rewritten
The class objects retrieved through the Runtime underlying API are the ones that ISA actually points to
And the class object obtained using the OC method is the return value of the class method overridden by the intermediate class
- From a development perspective. We use the
Person
Class generates instance objects, which we recognize as typesPerson
Although the system generates intermediate classes in the middle, itsisa
The orientation changed, but Apple didn’t want to expose the middle class directly. So the middle class is rewrittenclass
Method, returns directlyPerson
Class. The advantage is that it hides the internal implementation, the implementation of this class, inOC
Consistency at the code level.- If subclasses do not override this method, call
class
Method, first throughisa
Point to a de-class objectNSKVONotifying_Person
Look for this method, not find a layer by layer throughsuperclass
Look up untilNSObject
Class, the default implementation is to return directlyreturn object_getClass(self)
Is equivalent to returning directlyNSKVONotifying_Person
This class object.- According to official documents,
isa
Pointers are the object classes that maintain the schedule table, which contains method Pointers to implementations, among other things. Property of the observed object when observedisa
It changes. So,isa
The actual class that does not necessarily reflect the actual instance object. Tell us not to rely onisa
Pointer to determine the type, instead, useclass
Method to determine the actual type of the object.
- (void)printMethodNameOfClass:(Class) CLS {unsigned int count = 0; NSMutableString *methodNames = [NSMutableString string]; Method *methodList = class_copyMethodList(cls, &count); for (int i = 0; i < count; i++) { Method method = methodList[i]; [methodNames appendString:NSStringFromSelector(method_getName(method))]; [methodNames appendString:@"\n"]; } free(methodList); NSLog(@"%@", methodNames); ⚠️ must be called after KVO is registered, otherwise an error will be reported, because this class will only be generated dynamically after KVO is registered. // [self printMethodNameOfClass:NSClassFromString(@"NSKVONotifying_Person")];Copy the code
CLS - (void) printSuperClassChainOfClass: (Class) {/ / registered the total number of int count = objc_getClassList (NULL, 0). NSMutableArray *mArray = [NSMutableArray arrayWithObject: CLS]; // 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(@"classes = %@", mArray); }Copy the code
4.2 Call Sequence Analysis
NSKVONotifying_Person: The subclass calls the Foundation framework method, which calls willChangeValueForKey, and the parent class. Finally, call didChangeValueForKey to notify the Observer.
4.3 What happens after the observer is removed?
The isa refers back to the original class after removing the observer, and the dynamic subclass is not destroyed; it still exists by printing.
5. Foundation
spy
According to the call sequence analysis, KVO called the _NSSetIntValueAndNotify method in the rewritten setter method, and this method belongs to the Foundation framework, we can not see its source code implementation, so we look at the internal implementation of Foundation from another perspective
View the Foundation location through image List debugging and find it to view through the Hopper disassembly tool
Delete the Int and search for _NSSetValueAndNotify, which brings up a number of method names of various types, proving that the various types of properties observed by KVO call different Foundation methods depending on the type of property.
Search for these method names by using the terminal command nm plus filter.
6. Manually Close & Manually sendKVO
When we are in a class and want to turn off KVO for an attribute or all of them (only one attribute can use KVO)
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{ return NO; } / / disabled for a certain key + (BOOL) automaticallyNotifiesObserversForKey: (nsstrings *) key {the if ([key isEqualToString: @ "Nick"]) {return NO; } return [super automaticallyNotifiesObserversForKey:key]; } / / easy way Directly to Nick properties prohibit + (BOOL) automaticallyNotifiesObserversOfNick {return YES; }Copy the code
-
Using automaticallyNotifiesObserversForKey return NO, all object properties can not KVO;
-
Simple writing, is based on the attributes of the object to rewrite method automaticallyNotifiesObserversOf < Key > form
-
If KVO is disabled for all attributes, but you want to use it for one attribute alone, you can trigger it manually
- (void)setNick:(NSString *)nick{ [self willChangeValueForKey:@"nick"]; _nick = nick; [self didChangeValueForKey:@"nick"]; } Copy the code
- useIn pairsthe
willChangeValueForKey
anddidChangeValueForKey
Manually triggered, ⚠️ if only calleddidChangeValueForKey
And it won’t trigger because it’s indidChangeValueForKey
Will judgewillChangeValueForKey
;
- useIn pairsthe
7. How do mutable arrays trigger KVO
We have another mutable array in Person
@interface LGPerson : NSObject @property (nonatomic, strong) NSMutableArray *dateArray; @end [self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL]; self.person = dateArray = [NSMutableArray arrayWithCapacity:1]; // Will this trigger KVO ???? [self.person.dateArray addObject:@1000];Copy the code
Obviously by [self.person.dateArray addObject:@1000]; This line of code does not trigger KVO, because KVO is essentially calling setter methods. It certainly doesn’t.
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@22];
LAST – Interview question
1. How does iOS implement KVO for an object? What is the nature of KVO?
- using
RuntimeAPI
Dynamically subclass and let instance objectsisa
Points to a newly generated subclass (i.eisa-swizzling
Technology) - Modifying a property calls the property by overriding it
setter
The method callFoundation
the_NSSetXXXValueNotify
Function,,,willChangeValueForKey
- The original implementation of the parent class
didChangeValueForKey
The listener is triggered internallyobserverValueForKeyPath:ofObject:chang:context
methods
- The dynamic subclass overrides the observation property
setter
methodsclass
Method,dealloc
Methods and AdditionsisKVOA
methods
2. How do I trigger KVO manually?
Trigger manually using pairs of willChangeValueForKey and didChangeValueForKey. DidChangeValueForKey alone cannot trigger either and must be wrapped in pairs.
3. Will direct modification of member variables penalize KVO?
Self. Person ->age = 10; By nature, you need to call the setter method
4. Will kVO be triggered by a call through KVC
[self.person setValue:@”zhangsna” forKey:@”name”]
It fires the setter method. So the KVO callback is triggered.