Write in the front: the exploration of the underlying principles of iOS is my usual development and learning in the continuous accumulation of a step forward road. I hope it will be helpful for readers to record my journey of discovery.Copy the code

The directory is as follows:

  1. Exploring the underlying principles of iOS alloc
  2. Exploration of the underlying principles of iOS structure in vivo alignment
  3. The nature of the object explored by iOS underlying principles & the underlying implementation of ISA
  4. The underlying principles of the ISA-class (part 1)
  5. The underlying principles of the ISA class (middle)
  6. The underlying principles of isA-Class (part 2)
  7. Exploring the nature of Runtime Runtime & Methods in iOS Underlying principles
  8. Objc_msgSend explores the underlying principles of iOS
  9. Runtime Runtime slow lookup process
  10. Dynamic method resolution for exploring the underlying principles of iOS
  11. IOS underlying principles to explore the message forwarding process
  12. IOS Application loading principle
  13. Application loading principle (2)
  14. IOS underlying principle exploration class load
  15. IOS Underlying principles to explore the classification of loading
  16. Associated object for iOS underlying principle exploration
  17. Exploration of iOS underlying principles of sorcerer KVC
  18. Exploring the underlying principles of iOS — KVO Principles | more challenging in August

Summary column for the above

  • Summary of the phase of iOS underlying principle exploration

preface

In the previous article, we explored and summarized the overall process of KVO. In general, it is relatively simple. So today, we will follow the PROCESS of KVO and write a KVO by hand.

KVO process

Before customizing THE KVO, let’s take the KVO process that was finalized in the last article, and then, following the Apple process, let’s define the implementation.

Custom KVO

addObserver:self forKeyPath:@property options:NSKeyValueObservingOptionNew context:NULL

  • Verify for existencesetterMethod: keep the instance out
- (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:@"There is no current setter for %@",keyPath] userInfo:nil]; }}Copy the code
  • Dynamically generated subclassSMKVONotifying_Obj
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"% @ % @",kSMKVOPrefix,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: add class: the class points to SMPerson
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class].classSEL);
    const char *classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)sm_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)sm_setter, setterTypes);
    return newClass;
}
Copy the code
  • willisaTo:SMKVONotifying_Obj
object_setClass(self, newClass);
Copy the code
  • Save the observer (by associating objects)
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kSMKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
Copy the code

With these four steps, we have implemented exactly what Apple did with addObserver.

Notice the four methods overridden

The key one of the four methods that we’re going to override is the setter method for the property, and we’re not going to implement it here, because the next step, when you change the value of the property, that’s what the implementation is going to do, so let’s just go ahead and implement the setProperty and that’s what we’re going to do in the flowchart, The overridden method and the reason for using dotted lines when changing property values in Part 2.

.property = newValue

The new value needs to be forwarded to the parent class after the parameter is obtained, because the corresponding attribute of the parent class also needs to change the new value.

The change in the property value is then called back to the property observer, apple’s observeValueForKeyPath method, to let the observer know that the property value has changed and to process the related business logic in the callback.

static void sm_setter(id self,SEL _cmd,id newValue){
    NSLog(@"To: % @",newValue);
    // Message forward: forward to the parent class
    // Change the value of the parent class -- you can cast it
    
    void (*sm_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)
    sm_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)(kSMKVOAssiociateKey));
    
    // 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

removeObserver:self forKeyPath:@property

Finally, when removing an observer, return the observer’s ISA to its original parent class:

- (void)sm_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    // Return to the parent class
    Class superClass = [self class];
    object_setClass(self, superClass);
}
Copy the code

supplement

Class methods and getter and setter methods.


static NSString *const kSMKVOPrefix = @"SMKVONotifying_";
static NSString *const kSMKVOAssiociateKey = @"kSMKVO_AssiociateKey";

Class sm_class(id self,SEL _cmd){
    returnclass_getSuperclass(object_getClass(self)); } #pragma mark === =>>> set key ===>> set key:static NSString *setterForGetter(NSString *getter){
    
    if (getter.length <= 0) { returnnil; } NSString *firstString = [[getter substringToIndex:1] uppercaseString];
    NSString *leaveString = [getter substringFromIndex:1];
    
    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString]; Set <Key>:===> Keystatic NSString *getterForSetter(NSString *setter){
    
    if (setter.length <= 0| |! [setter hasPrefix:@"set") | |! [setter hasSuffix:@":"]) { returnnil; } 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];
}
Copy the code

conclusion

Through the three steps above:

  • When adding an observation attribute, override the instance variable, dynamically subclass, point the observer’s ISA to the generated subclass SMKVONotifying_Objc, and override the four methods.
  • The focus is on the setProperty method, which sends a message to the parent class to modify the instance variable of the parent class, and calls back to the observer change method to notify the observer of the property change.
  • Finally, when removing observations, put ISA back into Objc for a perfect closed loop. (Dynamically generated subclasses will not be removed)