美文网首页selector
iOS底层原理:KVO简析&自定义

iOS底层原理:KVO简析&自定义

作者: 木槿WEIXIAO | 来源:发表于2021-07-13 22:38 被阅读0次

    1.KVO的简单使用

    • 三部曲
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        self.person = [YXPerson alloc];
        //1.添加监听
        [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
    }
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        self.person.name = [NSString stringWithFormat:@"%@+",self.person.name];
    }
    //2.响应监听
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        NSLog(@"新值- %@",[change objectForKey:NSKeyValueChangeNewKey]);
    }
    //移除监听
    - (void)dealloc
    {
        [self.person removeObserver:self forKeyPath:@"name"];
    }
    

    自动开关

    @implementation YXPerson
    //自动开关
    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
    {
        if ([key isEqualToString:@"name"]) {
            return NO;//返回NO,就监听不到
        }
        return YES;
    }
    - (void)setName:(NSString *)name
    {
        //手动开关,自动开关关闭的时候,可以通过手动开关来打开监听
    //    [self willChangeValueForKey:@"name"];
        _name = name;
    //    [self didChangeValueForKey:@"name"];
    }
    
    @end
    

    KVO观察数组

    • 数组的观察比较特殊,需要用到KVC的方式进行改变
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        [[self.person mutableArrayValueForKey:@"myArray"] addObject:@"1"];
    }
    

    原理

    • 1.动态生成子类:NSKVONotifiy_A
      • 1.防止实例变量的影响
      • 2.动态子类的过程A:生成类B:添加class方法C:注册
      • 3.对类的存在性进行判断
    • 2.给动态子类添加setter方法
    • 3.消息转发给父类
    • 验证过程需要以下方法,有兴趣的同学可以自己验证
    #pragma mark - 打印类所有的方法
    - (void)printAllMethodsWithCls:(Class)cls
    {
        unsigned int count = 0;
        Method *methods = class_copyMethodList(cls, &count);
        for (int i = 0; i < count; i ++) {
            Method method = methods[i];
            SEL sel = method_getName(method);
            NSLog(@"方法名- %@",NSStringFromSelector(sel));
        }
        free(methods);
    }
    #pragma mark - 遍历类以及子类
    - (void)printClass:(Class)cls
    {
        //注册类的总数
        int count = objc_getClassList(NULL, 0);
        //创建一个数组,其中包含给定对象
        NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
        //获取所有已注册的类
        Class *classes = (Class *)malloc(sizeof(Class)*count);
        objc_getClassList(classes, count);
        for (int i = 0; i < count; i ++) {
            if (cls == class_getSuperclass(classes[i])) {
                [mArray addObject:classes[i]];
            }
        }
        free(classes);
        NSLog(@"classes = %@",mArray);
    }
    

    简单自定义KVO

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    typedef void (^YXKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
    
    @interface NSObject (YXKVO)
    - (void)yx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(YXKVOBlock)block;
    - (void)yx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "NSObject+YXKVO.h"
    #import <objc/message.h>
    
    static NSString *const k_YXKVOPrefix = @"YXKVONotifying_";
    static NSString *const k_YXKVOAssiociateKey = @"kYXKVO_AssiociateKey";
    
    @interface YXInfo : NSObject
    @property(nonatomic,weak)NSObject * observer;
    @property(nonatomic,copy)NSString * keyPath;
    @property(nonatomic,copy)YXKVOBlock handelBlock;
    @end
    @implementation YXInfo
    - (instancetype)initWithObserver:(NSObject *)observer keyPath:(NSString *)keyPath handelBlock:(YXKVOBlock)handelBlock
    {
        if (self == [super init]) {
            _observer = observer;
            _keyPath = keyPath;
            _handelBlock = handelBlock;
        }
        return self;
    }
    @end
    
    @implementation NSObject (YXKVO)
    - (void)yx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(YXKVOBlock)block
    {
        //1.验证是否存在set方法
        [self judgeSetMethodFormKeyPath:keyPath];
        
        //2.动态生成子类
        Class newClass = [self creatChildClassWithKeyPath:keyPath];
        
        //添加set方法
        SEL setSel = NSSelectorFromString([self setterForGetter:keyPath]);
        Method setMetod = class_getInstanceMethod([self class], setSel);
        const char *setType = method_getTypeEncoding(setMetod);
        class_addMethod(newClass, setSel, (IMP)yx_setter, setType);
        
        //3.更改isa指向
        object_setClass(self, newClass);
        
        //4.保存信息
        YXInfo *info = [[YXInfo alloc] initWithObserver:observer keyPath:keyPath handelBlock:block];
        NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey));
        if (!mArray) {
            mArray = [NSMutableArray arrayWithCapacity:1];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        [mArray addObject:info];
    }
    - (void)yx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
        
        NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey));
        if (observerArr.count<=0) {
            return;
        }
        
        for (YXInfo *info in observerArr) {
            if ([info.keyPath isEqualToString:keyPath]) {
                [observerArr removeObject:info];
                objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
                break;
            }
        }
        
        if (observerArr.count<=0) {
            // 指回给父类
            Class superClass = [self class];
            object_setClass(self, superClass);
        }
    }
    #pragma mark - 验证是否存在set方法
    - (NSString *)setterForGetter:(NSString *)keyPath
    {
        if (keyPath.length < 0) {
            return @"";
        }
        NSString *firstString = [[keyPath substringToIndex:1] uppercaseString];
        NSString *leaveString = [keyPath substringFromIndex:1];
        return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
    }
    - (void)judgeSetMethodFormKeyPath:(NSString *)keyPath
    {
        Class superClass = object_getClass(self);
        SEL setterSel = NSSelectorFromString([self setterForGetter:keyPath]);
        Method setterMethod = class_getInstanceMethod(superClass, setterSel);
        if (!setterMethod) {
            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"当前没有%@的set方法",keyPath] userInfo:nil];
        }
    }
    #pragma mark - 动态生成子类
    - (Class)creatChildClassWithKeyPath:(NSString *)keyPath
    {
        NSString *oldClassName = NSStringFromClass([self class]);
        NSString *newClassName = [NSString stringWithFormat:@"%@%@",k_YXKVOPrefix,oldClassName];
        Class newClass = NSClassFromString(newClassName);
        if (newClass) {
            return newClass;
        }
        
        //申请类
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        
        //注册类
        objc_registerClassPair(newClass);
        
        //添加class方法
        SEL classSel = NSSelectorFromString(@"class");
        Method classMethod = class_getInstanceMethod([self class], classSel);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSel, (IMP)yx_class, classTypes);
        
        //添加dealloc
        SEL deallocSEL = NSSelectorFromString(@"dealloc");
        Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
        const char *deallocType  = method_getTypeEncoding(deallocMethod);
        class_addMethod(newClass, deallocSEL, (IMP)yx_dealloc, deallocType);
        
        return newClass;
    }
    Class yx_class(id self,SEL _cmd){
        return class_getSuperclass(object_getClass(self));
    }
    static void yx_setter(id self,SEL _cmd,id newValue){
        NSLog(@"子类set");
        NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
        id oldValue = [self valueForKey:keyPath];
        //消息转发:转发给父类
        //改变父类的值
        void(*yx_msgSendSuper)(void *,SEL,id) = (void *)objc_msgSendSuper;
        struct objc_super superStruct = {
            .receiver = self,
            .super_class = class_getSuperclass(object_getClass(self))
        };
        yx_msgSendSuper(&superStruct,_cmd,newValue);
        
        //数据回调
        NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey));
        for (YXInfo *info in mArray) {
            if ([info.keyPath isEqualToString:keyPath] && info.handelBlock) {
                info.handelBlock(info.observer, keyPath, oldValue, newValue);
            }
        }
    }
    static void yx_dealloc(id self,SEL _cmd){
    
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
    #pragma mark - 从set方法获取getter方法的名称 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];
    }
    @end
    

    相关文章

      网友评论

        本文标题:iOS底层原理:KVO简析&自定义

        本文链接:https://www.haomeiwen.com/subject/xmkrpltx.html