review
In the first two blogs, we have introduced the relevant operations of KVO, and the underlying logic of KVO is realized by dynamically generating subclasses and rewriting parent classes. How can we customize a KVO?
KVO (I) - An Introduction to KVO
KVO (II) - KVO principle analysis of iOS underlying exploration
1. Preliminary analysis
KVO of the system expands some capabilities on NSObject, as shown in the following figure:
The trilogy used by KVO of the system is:
- Add listening addObserver
- Listen for callback observeValueForKeyPath
- Remove listener removeObserver
We also customize a KVO based on the API of the system, as follows:
@interface NSObject (JP_KVO) - (void)jp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; - (void)jp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context; - (void)jp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; @end
We customize KVO to observe attributes, so there is no setter method for member variables, so the first step is to filter out member variables. How to dynamically generate subclasses, change the direction of isa and save observers, the steps are as follows:
- Verify whether there is a setter method: do not let member variables (instance variables) in
- Dynamically generate subclasses
- Change the direction of subclass isa
- Save our observers
- (void)jp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{ // 1: Verify whether there is a setter method: do not let the instance in [self judgeSetterMethodFromKeyPath:keyPath]; // 2: Dynamically generate subclasses Class newClass = [self createChildClassWithKeyPath:keyPath]; // 3: Direction of ISA object_setClass(self, newClass); // 4: Save observer objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(KJPKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
2. Verify whether setter method exists
- judgeSetterMethodFromKeyPath:keyPath
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{ Class superClass = object_getClass(self); SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath)); Method setterMethod = class_getInstanceMethod(superClass, setterSeletor); if (!setterMethod) { @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Sorry, there is no current%@of setter",keyPath] userInfo:nil]; } }
Judge whether there is a setter method, and throw exception information if there is no setter method
3. Dynamically generate subclasses
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{ NSString *oldClassName = NSStringFromClass([self class]); NSString *newClassName = [NSString stringWithFormat:@"%@%@",kjpKVOPrefix,oldClassName]; Class newClass = NSClassFromString(newClassName); // Prevent duplicate creation and generation of new classes if (newClass) return newClass; /** * If memory does not exist, create a build * Parameter 1: parent class * Parameter 2: name of the new class * Parameter 3: additional space for the development of new classes */ // 2.1: Application newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0); // 2.2: Registration objc_registerClassPair(newClass); // 2.3. 1: Add Class: the class points to JPPerson SEL classSEL = NSSelectorFromString(@"class"); Method classMethod = class_getInstanceMethod([self class], classSEL); const char *classTypes = method_getTypeEncoding(classMethod); class_addMethod(newClass, classSEL, (IMP)jp_class, classTypes); // 2.3. 2: add setter SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath)); Method setterMethod = class_getInstanceMethod([self class], setterSEL); const char *setterTypes = method_getTypeEncoding(setterMethod); class_addMethod(newClass, setterSEL, (IMP)jp_setter, setterTypes); return newClass; }
- JP_class returns parent class information
Class JP_class(id self,SEL _cmd){ return class_getSuperclass(object_getClass(self)); }
- Judge whether the subclass has been created, and create it if not
- Application category
- Registration class
- If the newClass does not exist, call objc_allocateClassPair creates a kvo subclass and overrides the - class method
- Add setter
- Return newClass
4. Create setter
Creating setter method is mainly divided into two parts: calling parent class method and sending notification
static void jp_setter(id self,SEL _cmd,id newValue){ // 4: Message forwarding: forward to parent class // Change the value of the parent class - you can cast void (*jp_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper; // void /* struct objc_super *super, SEL op, ... */ struct objc_super superStruct = { .receiver = self, .super_class = class_getSuperclass(object_getClass(self)), }; //objc_msgSendSuper(&superStruct,_cmd,newValue) jp_msgSendSuper(&superStruct,_cmd,newValue); // Now that we have observed it, the next step is not to call back -- let our observer call // - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context // 1: Get the observer id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kJPKVOAssiociateKey)); // 2: The message is sent to the observer SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:); NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd)); objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL); }
-
You can call the parent class method through objc_msgSendSuper implementation, call the setter of the parent class (you can also call it through performSelector
-
Notify the observer that the keypath can be accessed through_ cmd conversion, object is self, change can also be obtained, and context can not be transmitted first. So the core is the acquisition of observer.
-
The observer is stored by associating objects
-
When you get the observer, you send the message to the observer for information callback processing
-
getterForSetter
#pragma mark - get the name of getter method from set method set < key >: = = = = > key 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]; return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString]; }
More content is constantly updated
🌹 Just like it 👍🌹
🌹 If you think you have something to gain, you can have a wave, collect + pay attention, comment + forward, so as not to find me next time 😁🌹
🌹 Welcome to exchange messages, criticize and correct each other 😁, Self improvement 🌹