This is the sixth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

review

In the previous two posts, we have introduced KVO operations, and the underlying logic of KVO is implemented by dynamically subclassing and overriding the parent class. So how do we customize a KVO?

KVO for iOS (1) — Introduction to KVO

Exploration of iOS Underlying KVO(ii) — Analysis of KVO principle

1. Preliminary analysis

KVO for the system extends some capabilities on top of NSObject, as shown below:

The trilogy of systems used by KVO is:

  • Add to monitoraddObserver
  • Listen to the callbackobserveValueForKeyPath
  • Remove the monitorremoveObserver

We also have a custom KVO modeled on the system API, 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
Copy the code

Our custom KVO is to observe the property, so there is no setter for the member variable, so the first step is to filter out the member variable. How to dynamically subclass, change the isa orientation, and save the observer is roughly as follows:

  1. Verify for existencesetterMethod: Keep member variables (instance variables) out
  2. Dynamically generated subclass
  3. Change the isa pointing of a subclass
  4. Save our observer
- (void)jp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
    
    // 1: verify that there is a setter method: do not let the instance in
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 2: dynamically generating subclasses
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3: isa pointing
    object_setClass(self, newClass);
    // 4: Save the observer
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(KJPKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
Copy the code

2. Verify that the 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:At sign, "Sorry, there's no setter for the current % at sign.",keyPath] userInfo:nil]; }}Copy the code

If there is a setter method, throw an exception

3. Dynamically subclass

- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@ % @ % @ "",kjpKVOPrefix,oldClassName];
    Class newClass = NSClassFromString(newClassName);
    // Prevent duplicate creation to generate new classes
    if (newClass) return newClass;
    /** * If memory does not exist, create a generation * parameter 1: the parent class * parameter 2: the name of the new class * parameter 3: the extra space opened by the new class */
    // 2.1: Application
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.2: registration class
    objc_registerClassPair(newClass);
    // 2.3.1 : 添加class : class的指向是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: adding setters
    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;
}
Copy the code
  • JP_class returns information about the parent class
Class JP_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}
Copy the code
  • Determine if subclasses have been created, if not
  • Apply for class
  • registered
  • newClassIf it does not existobjc_allocateClassPaircreatekvoSubclass, and rewrite- classmethods
  • addsetter
  • returnnewClass

4. Create a setter

There are two main parts to creating setter methods: calling superclass methods and sending notifications

static void jp_setter(id self,SEL _cmd,id newValue){
    // 4: message forwarding: forwarding to the parent class
    // Change the value of the parent class -- you can cast it
    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, the next step is to call back -- for our observer to 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);
}

Copy the code
  • Calling a superclass method can be done by objc_msgSendSuper, and calling a setter for the superclass (or by performing Selector)

  • Inform the observer that the keypath can be obtained through the _cmd transformation, that object is self, that change can also be obtained, and that context can be left alone. The core is the acquisition of an observer.

  • The observer is stored as an associated object

  • Get the observer, then send the message to the observer, information callback processing

  • getterForSetter

#pragmaMark - Gets the name set of the getter method from the set method<Key>:===> key
static NSString *getterForSetter(NSString *setter) {if (setter.length <= 0| |! [setter hasPrefix:@"set") | |! [setter hasSuffix:@ ","]) { return nil; }NSRange range = NSMakeRange(3.setter.length4 -);
    NSString *getter = [setter substringWithRange:range];
    NSString *firstString = [[getter substringToIndex:1] lowercaseString];
    return  [getter stringByReplacingCharactersInRange:NSMakeRange(0.1) withString:firstString];
}
Copy the code

More to come

🌹 just like it 👍🌹

🌹 feel have harvest, can come a wave, collect + concern, comment + forward, lest you can’t find me next 😁🌹

🌹 welcome everyone to leave a message to exchange, criticize and correct, learn from each other 😁, improve self 🌹