“This is the 8th day of my participation in the August More Text Challenge. For details, see: August More Text Challenge.”

A.KVO custom ideas

  1. isaPointing to dynamic subclass
  2. Subclass many methods
  3. deallocclasssetNameLGPerson- Message callback
  4. remove

KVO custom dynamic subclass generation

1. Verify that the setter method exists

#pragma mark - verify that setters exist - (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:@"Old iron has no setter for the current %@",keyPath] userInfo:nil]; }}Copy the code

2.KVO core – ISA_SiwZiling dynamically generated subclass

- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"% @ % @",kLGKVOPrefix,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 LGPerson
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class].classSEL);
    const char *classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)lg_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)lg_setter, setterTypes);
    return newClass;
}
Copy the code
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];
}
Copy the code

Custom setters for KVO

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"To: % @",newValue);
    // 4: message forwarding: forwarding to the parent class
    // Change the value of the parent class -- you can cast it
    
    void (*lg_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)
    lg_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)(kLGKVOAssiociateKey));
    
    // 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

4. Custom KVO multi-element observation and removal observation

// 4: saves observer information
    LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    
    if(! observerArr) { observerArr = [NSMutableArray arrayWithCapacity:1];
        [observerArr addObject:info];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
Copy the code
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    if (observerArr.count<=0) {
        return;
    }
    
    for (LGKVOInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            [observerArr removeObject:info];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break; }}if (observerArr.count<=0) {
        // Return to the parent class
        Class superClass = [self class]; object_setClass(self, superClass); }}Copy the code
  // 1: Get the observer
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    
    for (LGKVOInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            dispatch_async(dispatch_get_global_queue(0.0), ^{
                NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
                // Handle the old and new values
                if (info.options & LGKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options & LGKeyValueObservingOptionOld) {
                    [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                    if(oldValue) { [change setObject:oldValue forKey:NSKeyValueChangeOldKey]; }}// 2: the message is sent to the observerSEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:); objc_msgSend(info.observer,observerSEL,keyPath,self,change,NULL); }); }}Copy the code

Custom functions of KVO

typedef void(^LGKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);

@interface NSObject (LGKVO)

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block;

- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

@end

Copy the code
@interface LGInfo : NSObject
@property (nonatomic, weak) NSObject  *observer;
@property (nonatomic, copy) NSString    *keyPath;
@property (nonatomic, copy) LGKVOBlock  handleBlock;
@end

@implementation LGInfo

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block{
    if (self=[super init]) {
        _observer = observer;
        _keyPath  = keyPath;
        _handleBlock = block;
    }
    return self;
}
@end


@implementation NSObject (LGKVO)

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block{
    
    // 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 points to: LGKVONotifying_LGPerson
    object_setClass(self, newClass);
    // 4: save the information
    LGInfo *info = [[LGInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    if(! mArray) { mArray = [NSMutableArray arrayWithCapacity:1];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [mArray addObject:info];
}

- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    if (observerArr.count<=0) {
        return;
    }
    
    for (LGInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            [observerArr removeObject:info];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break; }}if (observerArr.count<=0) {
        // Return to the parent class
        Class superClass = [self class]; object_setClass(self, superClass); }} #pragma mark - verify that setters exist - (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:@"Old iron has no setter for the current %@",keyPath] userInfo:nil];
    }
}

#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"% @ % @",kLGKVOPrefix,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 LGPerson
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class].classSEL);
    const char *classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)lg_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)lg_setter, setterTypes);
    return newClass;
}

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"To: % @",newValue);
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    // 4: message forwarding: forwarding to the parent class
    // Change the value of the parent class -- you can cast it
    void (*lg_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)
    lg_msgSendSuper(&superStruct,_cmd,newValue);
    
    // 5: information data callback
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    
    for (LGInfo *info in mArray) {
        if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
            info.handleBlock(info.observer, keyPath, oldValue, newValue);
        }
    }
}

Class lg_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

Custom KVO self-destruct


+ (BOOL)kc_hookOrigInstanceMenthod:(SEL)oriSEL newInstanceMenthod:(SEL)swizzledSEL {
    Class cls = self;
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    
    if(! swiMethod) {return NO;
    }
    if(! oriMethod) { class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod)); method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ })); } BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }
    return YES;
}


+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        [self kc_hookOrigInstanceMenthod:NSSelectorFromString(@"dealloc") newInstanceMenthod:@selector(myDealloc)];
    });
}

- (void)myDealloc{

    Class superClass = [self class];
    object_setClass(self, superClass);
    [self myDealloc];
}
Copy the code

Vii. Realization idea of FBKVO