Basic Implementation Principles of iOS KVO (1)
-
- A brief description of KVO
- Two, brief introduction of KVC
-
-
- 1. The KVC definition
- 2. Method calls
- 3. The rule of KVC
-
- Three, the implementation of KVORichard explore
-
-
- 1. Explore the underlying implementation principle of KVO
- 2. Analysis of KVO underlying implementation
-
- Fourth, the underlying principle of KVO
- Five, KVO underlying implementation code
-
-
- 1. Implement KVO listening by yourself through code
- 2. Use the Runtime to dynamically create subclasses
-
A brief description of KVO
The full name of KVO is key-value Observing, commonly known as “key-value monitoring”, which can be used to monitor the change of an object’s attribute Value. Explore with questions:
1. How does iOS implement KVO for an object? (What is the nature of KVO?)
A. When an object uses a KVO listener, iOS changes the isa pointer to that object to point to a new Runtime subclass that has its own implementation of the set method. The set implementation internally calls the willChangeValueForKey method, the original setter implementation, didChangeValueForKey method, And internal didChangeValueForKey method will be called the listener observeValueForKeyPath: ofObject: change: context: monitoring method.Copy the code
2. How do I manually trigger KVO
A. KVO is automatically triggered when the value of the monitored property is changed. If we want to trigger KVO manually, we need to call the willChangeValueForKey and didChangeValueForKey methods ourselves to trigger KVO manually without changing the property value, and both methods are necessary.Copy the code
3. What is the underlying KVO implementation? 4. Does changing the value of a member variable start KVO? 5. Does KVC assignment start KVO? 6.
Two, brief introduction of KVC
1. The KVC definition
KVC: Key-value coding is a mechanism for indirectly accessing anobject’s attributes and relationships using string identifiers. Key-value encoding is not a direct way of starting accessor methods and accessing instance variables, but a structure of indirectly accessing object attribute values using strings representing attributes.
Any accessor method, declared property, or instance variable that exists can be accessed by specifying its name as a string.
Key-value encoded access is concatenated:
-
The string as the key can be determined at run time
-
The consumer has no way of knowing how to actually access the property
Keys encoding necessary method in informal agreement NSKeyValueCoding statement (header file Foundation/NSKeyValueCoding. H). The default is implemented in NSObject.
2. Method calls
The following two methods are called to illustrate:
(id) valueForKey: (NSString *) key
Copy the code
Returns the value corresponding to the key string representing the property. If the value cannot be obtained, the receiver is caused to call the method valueForUndefinedKey:.
(void)setValue: (id) value forKey: (NSString*) key
Copy the code
Sets the value of the property corresponding to the key string key to value. Cannot set properties, will cause the receiver call methods setValue: ForUndefinedKey:. When executed, properties with accessors use accessors, and properties without accessors can also set values and access. Because both methods are instance methods, instance variables can be accessed from within the method body.
The access process is as follows:
-
If the receiver has a key accessor (or getKey, isKey, _key, _getKey, setKey)
-
It is used.
-
There is no accessor, query by using accessInstanceVariablesDirectly receiver class method. When you return YES, the instance variable key (or _key, isKey, _isKey, and so on) is returned or set. With reference counting, if the instance variable is an object, the old value is automatically released and the new value is saved and consolidated
-
Plug in.
-
(BOOL)accessInstanceVariablesDirectly
Usually defined to return YES, which can be changed in subclasses. When the class method returns YES, the instance variables of the class are accessed using the key-value encoding. Not accessible when NO is returned. As long as the method returns YES, the visual property of the instance variable is fine, even with the @private modifier
-
Access.
-
No visitor no instance variables, will cause the receiver call methods valueForUndefinedKey: or setValue: forUndefinedKey:.
(id) valueForUndefinedKey: (NSStirng *) key
This method is called from the method valueForKey: when the value corresponding to the key string cannot be obtained. By default, execution of this method triggers an NSUndefinedKeyException. However, by changing the definition in a subclass, you can return other objects.
(void) setValue: (id) value forUndefinedKey: (NSString *) key
Copy the code
This method is called from the setValue:forKey method when the attribute value corresponding to the key string cannot be set. By default, execution of this method triggers the NSUndefinedKeyException. However, you can return other objects by changing the definition in a subclass.
- If the return value is not an object, the value wrapped with the appropriate object is returned; Setting values should also wrap the corresponding objects first.
When the property is an object, the object may also hold properties. You can use “. Concatenation represents a string of keys, called a key path. It doesn’t matter how long the point or key is as long as you can find the object.
(id) valueForKeyPath:(NSString *) keyPath
Copy the code
Point the key path and send the valueForKey: method to the sink using the first key. Then, using the next key in the key path, the valueForKey: method is sent to the resulting object, and so on, returning the last obtained object.
(void)setValue: (id) value forKeyPath:(NSString *) keyPath
Copy the code
Fetching the object is the same as the valueForKeyPath: method, where only the setValue: forKey: method is called for the last key in the path and the property value is set to value.
3. The rule of KVC
-
Varies with accessor methods.
-
Use setValue: forKey: and keys to change. It may not be accessed at this time
-
Device.
-
Use setValue: forKeyPath: and key paths to change. It may not go through the accessor at this point. Not only the properties of the final monitored object, but also when the properties in the path change.
Third, exploring the realization principle of KVO
-
Firstly, we need to know the basic usage of KVO. The full name of KVO is key-value Observing, commonly known as “key-value monitoring”, which can be used to monitor the change of an object’s attribute Value. KVO: key-value observing, which is realized on the basis of KVC, is a mechanism for notifying other objects when the properties of an object change. Property changes can be monitored only if the accessor or instance variable is accessed using the KVC criteria. You cannot monitor when you change the value of an instance variable directly within a method.
-
(void)viewDidLoad { [super viewDidLoad]; Person *p1 = [[Person alloc] init]; Person *p2 = [[Person alloc] init]; p1.age = 1; p1.age = 2; p2.age = 2; / / the self monitoring the age attribute of the p1 NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[p1 addObserver:self forKeyPath:@”age” options:options context:nil]; p1.age = 10; [p1 removeObserver:self forKeyPath:@”age”];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{NSLog (@ “listening to % @ % @ changed the % @”, object, keyPath, change); }
<Person: 0x604000205460> {kind = 1; new = 10; old = 2; }
-
As you can see from the code above, after adding a listener, the value of the age property is notified to the listener when it changes, executing the listener’s observeValueForKeyPath method.
1. Explore the underlying implementation principle of KVO
We can go to the Person class and override the set method of age to see if KVO has done something inside the set method to notify the listener. We find that even if we override the set method, p1 and P2 call the same set method, but we find that P1 also executes the observeValueForKeyPath method of the listener in addition to calling the set method. Note that KVO has made some changes to the P1 object at runtime. So p1 might have done something extra when setage was called. So the problem is that the objects are different in memory. They might be different in nature. Let’s explore how this is done inside KVO.
2. Analysis of KVO underlying implementation
- First, let’s look at the interrupt point where we added the listener in the code above. What does the addObserver method do to the p1 object? That is, what happens to the P1 object after it passes through the addObserver method by printing the ISA pointer as shown below
According to the figure above, after the addObserver operation is performed on p1 object, the ISA pointer of P1 object changes from Person to NSKVONotifyin_Person, while p2 object does not change at all. That is, once the KVO listener is added to p1, the ISA pointer will change, so the set method will not perform the same.
So let’s first look at how p2 objects are stored in the content, and then compare p1 to P2. First of all, when P2 calls setage, it finds the Person class object through the ISA pointer in the P2 object, and then finds the Setage method in the class object. Then find the corresponding implementation of the method. As shown in the figure below
NSKVONotifyin_Person isa subclass of Person. NSKVONotifyin_Person is generated by the Runtime at runtime. Therefore, when p1 object calls setage method, it must find NSKVONotifyin_Person according to P1 ISA, and find the method and implementation of setage in NSKVONotifyin_Person.
After consulting the data, we can learn. The setage method in NSKVONotifyin_Person actually calls the Fundation C function _NSsetIntValueAndNotify, and what it does internally is say, First call willChangeValueForKey will change the method, then call the setage method of the parent class to assign a value to a member variable, and finally call didChangeValueForKey to change the method. The listener’s listener method is called in didChangeValueForKey, which ends up in the listener’s observeValueForKeyPath method.
- So how do you verify that KVO is implemented in the way described above?
We have already verified that the isa pointer points to a subclass of Person created by Runtime, NSKVONotifyin_Person, when executing the listener.
In addition, we can print the address of the method to see how the address of p1 and P2 setage method changes before and after adding KVO.
NSLog(@" before adding KVO listener -p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]); NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [p1 addObserver:self forKeyPath:@"age" options:options context:nil]; NSLog(@" after adding KVO listener -p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);Copy the code
We found that before KVO listening was added, the address of P1 and P2’s setAge method was the same, but after KVO listening, the address of P1’s setAge method was changed. We used the printing method to realize the change and found that it was exactly the same as what we said above. The implementation of THE setAge method of P1 is transformed by the setAge method of the Person class method into the _NSsetIntValueAndNotify function of the Foundation framework of C language.
The Foundation framework calls different methods depending on the type of property. For example, with the age property of type int we defined earlier, we see the _NSsetIntValueAndNotify function called in the Foundation framework. So let’s change the property type of age to double and reprint it
We found that the function called into _NSSetDoubleValueAndNotify, so this suggests that the Foundation framework has a lot of this type of function, through the different types of different function called attributes. So we can assume that there are many other Foundation frameworks such as _NSSetBoolValueAndNotify, _NSSetCharValueAndNotify, _NSSetFloatValueAndNotify, and _NSSetLongValueAn DNotify and so on. You can go to the Foundation framework file and query the keywords on the command line to find relevant functions
- How does NSKVONotifyin_Person work internally?
NSKVONotifyin_Person is a subclass of Person whose superclass pointer points to the Person class, and NSKVONotifyin_Person must have a separate implementation of the setAge method. The difference between NSKVONotifyin_Person and the Person class may lie in the object methods and implementation of NSKVONotifyin_Person.
We print the Person object and the object methods stored in the NSKVONotifyin_Person object via Runtime
- (void)viewDidLoad { [super viewDidLoad]; Person *p1 = [[Person alloc] init]; P1. The age = 1.0; Person *p2 = [[Person alloc] init]; P1. The age = 2.0; / / the self monitoring the age attribute of the p1 NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [p1 addObserver:self forKeyPath:@"age" options:options context:nil]; [self printMethods: object_getClass(p2)]; [self printMethods: object_getClass(p1)]; [p1 removeObserver:self forKeyPath:@"age"]; } - (void) printMethods:(Class)cls { unsigned int count ; Method *methods = class_copyMethodList(cls, &count); NSMutableString *methodNames = [NSMutableString string]; [methodNames appendFormat:@"%@ - ", cls]; for (int i = 0 ; i < count; i++) { Method method = methods[i]; NSString *methodName = NSStringFromSelector(method_getName(method)); [methodNames appendString: methodName]; [methodNames appendString:@" "]; } NSLog(@"%@",methodNames); free(methods); }Copy the code
The above print content is as follows:
Using the above code we see that there are four object methods in NSKVONotifyin_Person. SetAge: class dealloc _isKVOA, so now we can draw the memory structure of NSKVONotifyin_Person and the method call order.
Here NSKVONotifyin_Person overrides the class method to hide NSKVONotifyin_Person. Hidden from the outside world. After adding the KVO listener to p1, we can print the class of p1 and P2 objects and see that they both return Person.
NSLog(@"%@,%@",[p1 class],[p2 class]); // Print the result Person,PersonCopy the code
If NSKVONotifyin_Person doesn’t override the class method, then when an object calls a class object method it will look all the way up to Nsobject, and the class implementation of Nsobect basically returns the class that its ISA points to, Return the class that ISA points to in P1 and the printed class is NSKVONotifyin_Person, but Apple doesn’t want to expose the NSKVONotifyin_Person class and doesn’t want us to know the internal implementation of NSKVONotifyin_Person, So I’ve internally overridden the class class to return the Person class directly, so when I call p1’s class object method, it’s the Person class. So p1 gives the impression that p1 is still a Person class and doesn’t know that the NSKVONotifyin_Person subclass exists.
So we can guess that the internal implementation of the class overwritten in NSKVONotifyin_Person is roughly as follows:
Return class_getSuperclass(object_getClass(self)); }Copy the code
- Internal validation didChangeValueForKey invokes the observer observeValueForKeyPath: ofObject: change: context: method.
We simulate their implementation by overriding the willChangeValueForKey: and didChangeValueForKey: methods in the Person class.
- (void)setAge:(int)age
{
NSLog(@"setAge:");
_age = age;
}
- (void)willChangeValueForKey:(NSString *)key
{
NSLog(@"willChangeValueForKey: - begin");
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey: - end");
}
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(@"didChangeValueForKey: - begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey: - end");
}
Copy the code
Run again to see what’s running inside the didChangeValueForKey method, as you can see by printing, Indeed inside didChangeValueForKey method has been called the observer observeValueForKeyPath: ofObject: change: context: method.
Fourth, the underlying principle of KVO
- First create a Person class with a name property inside, then create two instance objects, p1 with kVO listener, P2 without kVO listener, and override observeValueForKeyPath to listen for Person.name Notification of property changes.
In essence, the setName method is called when Person assigns a value to name. The setName method is the same whether P1 or P2 calls the setter method. Why is p1 notified when it changes the value of the name property? P2 does not call setName:(NSString *)name.
- I’m going to print the memory addresses of P1 and P2 and see if I can figure out what the memory addresses of P1 and P2 are.
-
From p1
-
And the P2 memory address doesn’t show anything. Next, print the class information for P1 and P2
-
Type o
-
Bject_getClass Try it, we all know that object_getClass(id) returns the actual class type of this instance object
-
Print s
-
EtName method IMP pointer has not changed, we know that the implementation of the same method IMP address is the same.
-
NSKVONotifying_Person and imp pointer after adding KVO
-
Investigate.
- Start by entering imp1 and imp2 on LLDB
Imp1 happened method in the Foundation framework _NSSetObjectValueAndNotify function, whereas imp2 calls the Person elegantly-named setName method
In other words, after adding KVO, P1 will not call Person setName method after modifying name value, while P2 will still call setName: method normally without adding KVO listener, which can be obtained after adding KVO listener to P1 The system changed the default method implementation, so since setName: method is not called what - Did the value of p1.name also change?
- NSKVONotifying_Person: NSKVONotifying_Person: NSKVONotifying_Person: NSKVONotifying_Person What is the connection between NSKVONotifying_Person and Person?
By printing the superclass of NSKVONotifying_Person and the superclass of Person, NSKVONotifying_Person is a Person subclass, so why would Apple create one dynamically? NSKVONotifying_Person subclass has nothing to do with what’s inside Person - The same?
Let’s output the list of methods and properties inside Person and NSKVONotifying_Person and see what methods and properties are added to the NSKVONotifying_Person subclass.
- (void)viewDidLoad { [super viewDidLoad]; Person *p1 = [[Person alloc] init]; Person *p2 = [[Person alloc] init]; id cls1 = object_getClass(p1); id cls2 = object_getClass(p2); NSLog(@" before adding KVO: cls1 = % @cls2 = %@ ",cls1,cls2); [p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL]; cls1 = object_getClass(p1); cls2 = object_getClass(p2); NSString *methodList1 = [self printPersonMethods:cls1]; NSString *methodList2 = [self printPersonMethods:cls2]; NSLog(@"%@",methodList1); NSLog(@"%@",methodList2); / / NSLog (@ ": after adding KVO cls1 = % @ cls2 = % @", cls1, cls2); // id super_cls1 = class_getSuperclass(cls1); // id super_cls2 = class_getSuperclass(cls2); // // NSLog(@"super_cls1 = %@ ,super_cls2 = %@",super_cls1,super_cls2); // // p1.name = @"dzb"; // p2.name = @"123"; } - (NSString *) printPersonMethods:(id)obj { unsigned int count = 0; Method *methods = class_copyMethodList([obj class],&count); NSMutableString *methodList = [NSMutableString string]; [methodList appendString:@"[\n"]; for (int i = 0; i<count; i++) { Method method = methods[i]; SEL sel = method_getName(method); [methodList appendFormat:@"%@",NSStringFromSelector(sel)]; [methodList appendString:@"\n"]; } [methodList appendFormat:@"]"]; free(methods); return methodList; }Copy the code
The following output is displayed:
NSKVONotifying_Person also has a setName: overrides the class and dealloc methods, _isKVOA. After p1 added kVO, the Runtime dynamically generated a subclass of NSKVONotifying_Person and overwrote the setName method, so setName must have done something inside to trigger ObserveForkeypath Listening methods.
- So what does the NSKVONotifying_Person subclass override setName do? Elegantly-named setName method is invoked the internal Foundation _NSSetObjectValueAndNotify function, in
- _NSSetObjectValueAndNotify inside:
-
- The first call is willChangeValueForKey
-
- Then assign to the name attribute
-
- value
-
- And finally, didChangeValueForKey
-
- Finally, the observer’s observeValueForKeyPath is called to tell the listener that the property value has changed.
- Because the Apple Foundation framework is not
- Open source, so we can still verify our conjecture by overriding Person’s willChangeValueForKey and didChangeValueForKey.
First, setName is not the first to be executed when we change the value of p1.name: This method, instead, calls willChangeValueForKey and then calls the setter method of the parent class to assign a value to the property, and then calls didChangeValueForKey and calls the
DidChangeValueForKey internally calls the observeValueForKeyPath method of the listener to tell the outside world that the property value has changed.
The dealloc and class methods were overridden to do some KVO to free up memory and hide the presence of the external subclass NSKVONotifying_Person
10. This is why we call [p1 class] and [p2 class] and both show the Person class, giving us the illusion that the Person has not changed
- When assigning a value to a property, KVC will look for _age, isAge, setAge, and tisage methods in this class. Finally, it will call the setter method of the property, so if KVO is added, it will still be triggered. Setting the member variable _age does not trigger the KVO code because it does not trigger setter methods.
Five, KVO underlying implementation code
1. Implement KVO listening by yourself through code
-
ViewController call implementation
#import “ViewController.h” #import “Person.h” #import “NSObject+KCKVO.h” #import “Dog.h”
@interface ViewController () @property (nonatomic, strong) Person *p; @end
// implementation ViewController
-
(void)viewDidLoad { [super viewDidLoad];
self.p = [[Person alloc] init]; [self.p lg_addObserver:self forKeyPath:@”name”]; self.p.name = @”kongyulu”;
}
Pragma mark-value callback
- (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object newValue:(id)newValue{ NSLog(@”lg_observeValueForKeyPath – %@”,newValue);
}
#pragma mark – dealloc
- (void)dealloc{
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.p.name = [NSString stringWithFormat:@”%@+”,self.p.name];
}
@end
-
-
Define two classes
-
Define the Person class
#import <Foundation/Foundation.h>
@interface Person : NSObject{ @public NSString *girl; }
@property (nonatomic, copy) NSString *name; @property (nonatomic, assign) int age;
@property (nonatomic, strong) NSMutableArray *mArray;
- (instancetype)shared;
@end
#import “Person.h”
@implementation Person
- (void)setName:(NSString *)name{NSLog(@” set method “);
}
- (void)dealloc{NSLog(@” parent left “);
}
@end #import “Person.h”
@implementation Person
- (void)setName:(NSString *)name{NSLog(@” set method “);
}
- (void)dealloc{NSLog(@” parent left “);
}
@end
-
Define the Dog class
#import <Foundation/Foundation.h> #import “Person.h”
@interface Dog : Person
@property (nonatomic, copy) NSString *name; @property (nonatomic, assign) int age;
@end
#import “Dog.h”
@implementation Dog
- (void)dealloc{NSLog(@” son is gone “);
} @end
- An implementation of KVO listening classes that define NSObject
-
The header file
#import <Foundation/Foundation.h>
typedef void(^KCKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
@interface NSObject (KCKVO)
-
(void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
-
(void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
-
-
The implementation class
#import “NSObject+KCKVO.h” #import <objc/message.h>
static NSString *const kKCKVOPrefix = @”KCKVO_”; static NSString *const kKCKVOAssiociateKey = @”kKCKVO_AssiociateKey”;
@implementation NSObject (KCKVO)
-
(void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{ // 1: // id superClassName = object_getClassName(self); // person // setName NSString *setterMethodName = setterForGetter(keyPath); // setName: SEL setterSel = NSSelectorFromString(setterMethodName); // method Method method = class_getInstanceMethod([self class], setterSel); // runtime 1900009931 if (! Method) {@ throw [[NSException alloc] initWithName: NSExtensionItemAttachmentsKey reason: @ “no setter method” the userInfo: nil]; }
/ / 2: the dynamically generated Class subclass childClass = [self creatChildClassWithKeypath: keyPath]; if (! ChildClass) {NSLog(@” create failed “); Objc_setAssociatedObject (self, (__bridge const void * _Nonnull)(kKCKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark – Create subclasses dynamically
-
(Class)creatChildClassWithKeypath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]); //person NSString *childClassName = [NSString stringWithFormat:@”%@%@”,kKCKVOPrefix,oldClassName];
/ / / / 2: dynamically generated subclass 2.1 application Class childClass = objc_allocateClassPair ([self Class], childClassName. UTF8String, 0). //2.2 Register class objc_registerClassPair(childClass); // add class SEL classSel = NSSelectorFromString(@”class”); Method classMethod = class_getClassMethod([self class], classSel); const char *classType = method_getTypeEncoding(classMethod); class_addMethod(childClass, classSel, (IMP)lg_Class, classType); //2.4 setName: setterSel setterSel = NSSelectorFromString(setterForGetter(keyPath)); Method setterMethod = class_getClassMethod([self class], setterSel); const char *setterType = method_getTypeEncoding(setterMethod); class_addMethod(childClass, setterSel, (IMP)lg_setter, setterType); Object_setClass (self, childClass); return childClass;
}
/** Check whether the method */ exists
-
(BOOL)hasSeletor:(SEL)selector{
Class observedClass = object_getClass(self); unsigned int methodCount = 0; // get a list of Method names //class_copyIvarList instance variables //class_copyPropertyList get all property names Method *methodList = class_copyMethodList(observedClass, &methodCount);
for (int i = 0; i<methodCount; i++) { SEL sel = method_getName(methodList[i]); if (selector == sel) { free(methodList); return YES; } } free(methodList); return NO;
}
Static Class lg_Class(id self,SEL _cmd){return class_getSuperclass(object_getClass(self)); }
static void lg_setter(id self,SEL _cmd,id value){
NSLog(@"lg_setter - %@",value); id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kKCKVOAssiociateKey)); SEL handlSEL = @selector(lg_observeValueForKeyPath: ofObject:newValue:); NSString *keypath = getterForSetter(NSStringFromSelector(_cmd)); objc_msgSend(observer,handlSEL,keypath,self,value); Copy the code
// [observer performSelector:@selector(lg_observeValueForKeyPath: ofObject:newValue:) withObject:self afterDelay:0]; }
Name ===>>> setName: static NSString * setterForGetter(NSString *getter){#pragma mark – Get set method name ===>>> setName: static NSString * setterForGetter(NSString *getter){
if (getter.length <= 0) { return nil; } NSString *firstString = [[getter substringToIndex:1] uppercaseString]; NSString *leaveString = [getter substringFromIndex:1]; return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString]; Copy the code
}
SetName :===> name static NSString * getterForSetter(NSString *setter){
if (setter.length <= 0 || ! [setter hasPrefix:@"set"] || ! [setter hasSuffix:@":"]) { return nil; } NSRange range = NSMakeRange(3, setter.length-4); NSString *getter = [setter substringWithRange:range]; NSString *firstString = [[getter substringToIndex:1] lowercaseString]; getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString]; return getter;Copy the code
}
@end
-
4. Run the printed result:
2. Use the Runtime to dynamically create subclasses
-
Dynamically create an NSKVONotifying_Person subclass
/** Create subclasses dynamically at runtime
@param super_cls superclass @return Returns a subclass */
- (Class) registerSubClassWithSuperClass: (Class) super_cls {/ / / dynamic create subclasses nsstrings * clsName = [nsstrings stringWithFormat:@”NSKVONotifying_%@”,super_cls]; / / / a default 16 bytes allocated memory Class NSObject sub_cls = objc_allocateClassPair (super_cls, clsName UTF8String, 16); // register a subclass objc_registerClassPair(sub_cls); // set the parent isa pointer to the subclass object_setClass(self, sub_cls); return sub_cls;
}
-
Dynamically add method setter method didChangeValueForKey class method implementation to this subclass dynamically
/ / / dynamically create subclasses NSKVONotifying_xxx Class sub_cls = [self registerSubClassWithSuperClass: super_cls];
Method class_method = class_getInstanceMethod(super_cls, @selector(class)); Method changeValue_method = class_getInstanceMethod(super_cls, @selector(didChangeValueForKey:)); class_addMethod(sub_cls, @selector(class), (IMP)kvo_class,method_getTypeEncoding(class_method)); DidChangeValueForKey class_addMethod(sub_cls, @selector(didChangeValueForKey), (IMP)didChangeValue,method_getTypeEncoding(changeValue_method)); // dynamically add setter method class_addMethod(sub_cls, setterSel, (IMP)kvo_setter,method_getTypeEncoding(method)); Objc_setAssociatedObject (self,(__bridge const void * _Nonnull)(KVOAssociatedObservers), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);Copy the code
-
Rewrite the class method implementation
/** self-implement the class method
NSKVONotifying_ static class kvo_class(id self,SEL _cmd) { return class_getSuperclass(object_getClass(self)); }
-
Rewrite setter method implementation
/** self-implementing setter methods
*/ static void kvo_setter(id self,SEL _cmd,id newValue) {
NSString *setterName = NSStringFromSelector(_cmd); NSString *getterName = getterForSetter(setterName); / / / is going to change the value of the attribute [self willChangeValueForKey: getterName]; Struct objc_super suer_cls = {.receiver = self, struct objc_super suer_cls = {.receiver = self, .super_class = class_getSuperclass(object_getClass(self)) }; Objc_setAssociatedObject (self,(__bridge const void * _Nonnull)(KVOAssociatedOldValue),[self valueForKey:getterName], OBJC_ASSOCIATION_RETAIN_NONATOMIC); /// call the parent setter method to set the newValue objc_msgSendSuper(&suer_cls,_cmd,newValue); / / / change after listening attribute value Call didChangeValueForKey and internal call [the self didChangeValueForKey: getterName];Copy the code
};
-
Override the didChangeValueForKey method implementation
/** didChangeValueForkey Static void didChangeValue(id self,SEL _cmd,NSString *key) {
id newValue = [self valueForKey:key]; id observer = objc_getAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedObservers)); id oldValue = objc_getAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedOldValue)); NSMutableDictionary *change = [NSMutableDictionary dictionary]; if (oldValue) { change[@"oldValue"] = oldValue; } else { change[@"oldValue"] = [NSNull null]; } if (newValue) { change[@"newValue"] = newValue; } else { change[@"newValue"] = newValue; } [observer observeValueForKeyPath:key ofObject:self change:change context:NULL]; Copy the code
}
The article will continue to be updated, you can also send me a message to get the latest information and interview information. If you have any comments and suggestions, please leave a message to me.
Like IOS partners attention! If you like, give a thumbs-up! Thank you very much! Thank you very much! Thank you very much!
Click to get:IOS Interview Materials
Features:The original