• Utility methods

    1. Returns the corresponding setter method name based on the key

      It’s a simple string concatenation

      Key => set< key > static NSString * setterForGetter(NSString *getter){if(getter. Length <= 0) return nil; NSString * firstString = [getter substringToIndex:1] uppercaseString]; NSString * otherString = [getter substringFromIndex:1]; NSString * otherString = [getter substringFromIndex:1]; return [NSString stringWithFormat:@"set%@%@:",firstString,otherString]; }Copy the code
    2. Get the corresponding key according to the corresponding setter method

      It’s a simple string interception

      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]; }Copy the code
  • Add observer method

    1. Verify that setter methods exist

      This step is relatively simple, that is, to query whether there is a method corresponding to the observed key, directly callclass_getInstanceMethodMethod throws an exception if it does not get the corresponding method

      // Verify that there is a setter method, If not directly throw an exception - (void) judgeSetterMethodFromKeyPath keyPath: (nsstrings *) {/ / because currently there is no point so here for a change of the isa is to call the Class is not the middle Class in the Class of the method currentClass = object_getClass(self); Sel setterSeleter = NSSelectorFromString(setterForGetter(keyPath)); SetterMethod = class_getInstanceMethod(currentClass, setterSeleter); if(! setterMethod){ @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString StringWithFormat :@" can't find setter method for %@ ",keyPath] userInfo:nil]; }}Copy the code
    2. Dynamically subclassing

      It is mainly divided into the following three steps:

      1. Determine whether an intermediate class already exists

        Since the intermediate class will not be destroyed once generated, it is necessary to obtain the corresponding class first and return it directly if it already exists (the process in the following steps is mainly for the process in which the intermediate class is created for the first time).
        NSString * oldClassName = NSStringFromClass([self class]); / / stitching middle class name nsstrings * newClassName = [nsstrings stringWithFormat: @ "% @ % @", kTDKVOPrefix, oldClassName]; Class newClass = NSClassFromString(newClassName); if(newClass) return newClass;Copy the code
      2. Apply for class

        callobjc_allocateClassPairmethods
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        Copy the code
      3. registered

        callobjc_registerClassPairmethods
        objc_registerClassPair(newClass);
        Copy the code
      4. Adding method (setterMethods andclassMethods)

        Setter methods and class methods are overridden methods of the parent class, so the method type is just the method type of the parent class
        SEL setterMethod = NSSelectorFromString(setterForGetter(keyPath)); Method method = class_getInstanceMethod([self class], setterMethod); const char * types = method_getTypeEncoding(method); class_addMethod(newClass, setterMethod, (IMP)td_setter, types); // add classSEL classSEL = NSSelectorFromString(@"class"); Method classMethod = class_getInstanceMethod([self class], classSEL); const char *classTypes = method_getTypeEncoding(classMethod); class_addMethod(newClass, classSEL, (IMP)td_class, classTypes);Copy the code
        • td_setterIn the article [Exploration of KVO Principle], it is known that the corresponding observation object will be notified when value is successfully modified, so we guess that the corresponding notification method is realized in the setter method, and KVO can also control whether to manually implement the observation, so the general process is as followsCorresponding code:
          // subclass override imp static void td_setter(id self,SEL _cmd,id newValue){// notify listener and execute parent set method NSLog(@" coming :%@",newValue); / * * 1. Two cases automaticallyNotifiesObserversForKey: automatic key observation method returns YES 2. AutomaticallyNotifiesObserversForKey: method returns NO need key observation method to realize the user's own * preparations / / / / / get the key nsstrings * setterMethodName = NSStringFromSelector(_cmd); NSString * keyPath = getterForSetter(setterMethodName); NSMutableArray * observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kTDKVOAssiociateKey)); for (NSUInteger i = 0, len = observerArray.count; i < len; i ++) { TDKVOInfo * info = observerArray[i]; If ([info. KeyPath isEqualToString: keyPath]) {/ / determines whether the current key value corresponding to the observation object information / / access to observe classes automaticallyNotifiesObserversForKey methods return values SEL automaticallySel = NSSelectorFromString(@"automaticallyNotifiesObserversForKey:"); /** Note: 1. Search for the value of objc_msgSend in Build Settings and change it to NO. Otherwise, the objc_msgSend call fails. Should be automaticallyNotifiesObserversForKey is a class method so objc_msgSend accepting object should be of the first parameter is the corresponding class rather than the instance objects, NSMutableDictionary * values = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kTDKVOAssiociateNewValues)); If (!) = if(!) = if(! values){ values = [[NSMutableDictionary alloc]init]; } values[keyPath] = newValue; values[[NSString stringWithFormat:@"old_%@",keyPath]] = [self valueForKey:keyPath]; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kTDKVOAssiociateNewValues), values, OBJC_ASSOCIATION_RETAIN_NONATOMIC); BOOL isAuto = objc_msgSend([info.observer class],automaticallySel,keyPath); If (isAuto) {/ / automatic key observation [self td_willChangeValueForKey: keyPath]; Struct objc_super superStruct = {.receiver = self, struct objc_superstruct = {.receiver = self, .super_class = class_getSuperclass(object_getClass(self)), }; SEL setterSel = NSSelectorFromString(setterForGetter(keyPath)); objc_msgSendSuper(&superStruct, setterSel,newValue); }}}Copy the code

          td_willChangeValueForKeymethods

          -(void)td_willChangeValueForKey:(NSString *)keyPath{ //1. Find all key observations from the associated object NSMutableArray * observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kTDKVOAssiociateKey)); for (NSUInteger i = 0, len = observerArray.count; i < len; i ++) { TDKVOInfo * info = observerArray[i]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1]; NSMutableDictionary * values = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kTDKVOAssiociateNewValues)); id newValue = values[keyPath]; id oldValue = values[[NSString stringWithFormat:@"old_%@",keyPath]]; if (info.options & TDKeyValueObservingOptionNew) { [change setObject:newValue forKey:NSKeyValueChangeNewKey]; } if (info.options & TDKeyValueObservingOptionOld) { [change setObject:@"" forKey:NSKeyValueChangeOldKey]; if (oldValue) { [change setObject:oldValue forKey:NSKeyValueChangeOldKey]; }} / / 2: a message sent to the observer SEL observerSEL = NSSelectorFromString (@ "td_observeValueForKeyPath: ofObject: change: context:"); objc_msgSend(info.observer,observerSEL,keyPath,[self superclass],change,NULL); }); }}Copy the code
        • td_classmethods

          In the articleKVO principle explorationIs obtained before adding the observerclassIs the current class, but after adding an observerclassStill the current class, but printedisaIt’s the middle class, and you know that the middle class is overwritten by printing the methods in the classclassMethod, so guess in rewriteclassMethod returnssuperClass, the specific implementation is as follows:
          Class td_class(id self,SEL _cmd){
              return class_getSuperclass(object_getClass(self));
          }
          Copy the code
    3. Change the ISA direction
      object_setClass(self, newClass);
      Copy the code
    4. Save observer information

      Because you can’t add attributes to a classification, you use associative objects

      TDKVOInfo * info = [[TDKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options]; // Because classification cannot add attributes, So I'm using the associative object NSMutableArray * observerArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kTDKVOAssiociateKey)); If (!) = if(!) = if(! observerArray){ observerArray = [NSMutableArray arrayWithCapacity:1]; [observerArray addObject:info]; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kTDKVOAssiociateKey), observerArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }Copy the code
  • Remove observer method

    The main task is to clear the corresponding observer information and modify the direction of ISA

    - (void)td_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{ //1. NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kTDKVOAssiociateKey)); if (observerArr.count<=0) { return; } for (TDKVOInfo *info in observerArr) { if ([info.keyPath isEqualToString:keyPath]) { [observerArr removeObject:info]; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kTDKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC); break; If (observerarr. count<=0) {// Refer to superClass = [self Class]; object_setClass(self, superClass); }}Copy the code

The source address