美文网首页
iOS 中的KVO 的原理以及应用

iOS 中的KVO 的原理以及应用

作者: helinyu | 来源:发表于2021-06-11 10:29 被阅读0次

    KVO 以及KVC的内容, 这些内容有什么?


    KVO 【key value observing】 键值监听 —— 用于监听某个对象属性值的改变。

    先来看一个例子

    代码段:
        self.person = [XNPerson new];
        self.person2 = [XNPerson new];
        [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    
    执行之后的结果

    PS : 可以看到person 的class已经发生了改变【person 增加了kvo的监听】, 而person2的没有变化。

    KVO的基本原理:

    1. 从上面,我们可以知道, kvo的实现修改了原来类的class类型[也就是修改isa指针],就是查找方法是从新的类对象去查找。
    2. 调用super的setter的方法,以及通知observevalue的通知方法, 当然这里还是可以实现通过willchangeKey: 那个也是可以处理的;

    自己去写一个有关的例子:

    @interface XNPerson : NSObject
    @property (nonatomic, copy) NSString *name;
    @end
    
    #import "NSObject+Category.h"
    #import <objc/runtime.h>
    #import <objc/message.h>
     
    static NSString *const LYKVO_ = @"LYKVO_notify_"; //专门做KVO的内容
    有关KVO的具体实现内容
    @implementation NSObject (Category)
    // 1) 调用super的setter方法
    // 2) 通知观察者
    void setName(id self, SEL _cmd, NSString *param) {
    //    objc_msgSendSuper(self, _cmd, param);
        
    //     保存子类类型
        id class = [self class];
        
    //     改变self的isa指针
        object_setClass(self, class_getSuperclass(class));
        objc_msgSend(self, @selector(setName:), param);
        
        id objc = objc_getAssociatedObject(self, "objc");
        NSMutableDictionary *change = [NSMutableDictionary new];
        [change addEntriesFromDictionary:@{@(NSKeyValueObservingOptionOld):[self performSelector:@selector(name)]}];
        [change addEntriesFromDictionary:@{@(NSKeyValueObservingOptionNew):param}];
        objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:), @"name", self,change,nil);
        object_setClass(self, class);
    }
    
    - (void)ly_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
    {
        if (keyPath.length <=0) return;
        
        NSString *superClsName = NSStringFromClass([self class]);
        const char * newClassName = [[@"LYKVO_" stringByAppendingString:superClsName] UTF8String];
        Class newCls = objc_allocateClassPair([self class], newClassName, 0);
        objc_registerClassPair(newCls);
        class_addMethod(newCls, @selector(setName:), (IMP)setName, "v@:@");
        object_setClass(self, newCls);
        objc_setAssociatedObject(self, "objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    @end
    

    参考例子

    PS: 通过集成子类来动态实现对应的内容,这样的方式也可以使用到修改按钮的点击范围, 或者说控件的点击范围等等操作。

    按钮点击范围扩展的例子:

    - (UIEdgeInsets)wk_expandEdgeInsets {
        
        NSValue * value = objc_getAssociatedObject(self, @selector(wk_expandEdgeInsets));
        return  value ? [value UIEdgeInsetsValue] : UIEdgeInsetsZero;
    }
    
    - (void)setWk_expandEdgeInsets:(UIEdgeInsets)wk_expandEdgeInsets {
        if (![NSStringFromClass(self.class) containsString:@"WKClickArearExpand_"]) {
            NSString *className = [NSString stringWithFormat:@"WKClickArearExpand_%@",self.class];
            Class kclass = objc_getClass([className UTF8String]);
            if (!kclass)
            {
    
                kclass = objc_allocateClassPair([self class], [className UTF8String], 0);
                SEL setterSelector = NSSelectorFromString(@"pointInside:withEvent:");
                Method setterMethod = class_getInstanceMethod([self class], setterSelector);
                const char *types = method_getTypeEncoding(setterMethod);
                class_addMethod(kclass, setterSelector, (IMP)WKClickArearExpand_pointInside, types);
                objc_registerClassPair(kclass);
            }
            object_setClass(self, kclass);
            
        }
        objc_setAssociatedObject(self, @selector(wk_expandEdgeInsets), [NSValue valueWithUIEdgeInsets:wk_expandEdgeInsets], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    static BOOL WKClickArearExpand_pointInside(id self, SEL _cmd, CGPoint point, UIEvent *event) {
        struct objc_super superclass = {
            .receiver = self,
            .super_class = class_getSuperclass(object_getClass(self))
        };
        BOOL (*objc_msgSendSuperCasted)(const void *, SEL,CGPoint, UIEvent*) = (void *)objc_msgSendSuper;
        
        UIEdgeInsets outerEdge = UIEdgeInsetsZero;
    
     // 这里判断的是有关的范围内容 , 修改点击的范围,我们修改对应的hittest的点的坐标
        if ([self isKindOfClass:[UIButton class]]) {
            UIButton * btn = (UIButton *)self;
            outerEdge = btn.wk_expandEdgeInsets;
            
            {// fix x
                BOOL needFixX = NO;
                CGFloat curX = 0.f;
                if ((point.x <0 &&((point.x +outerEdge.left)>=0))) {
                    curX = point.x + outerEdge.left;
                    needFixX = YES;
                }
                else if ((point.x >btn.width) && (point.x <= (btn.width +outerEdge.right))) {
                    curX = point.x;
                    needFixX = YES;
                }
                else {}
                if (needFixX) {
                    CGFloat width = btn.width;
                    if (width!=0) {
                        curX = (NSInteger)curX % (NSInteger)width;
                    }
                    point.x = curX;
                }
            }
          
            { // fix y
                BOOL needFixY = NO;
                CGFloat curY = 0.f;
                if ((point.y <0 &&((point.y +outerEdge.top)>=0))) {
                    curY = point.y + outerEdge.top;
                    needFixY = YES;
                }
                else if ((point.y >btn.height) && (point.y <= (btn.height +outerEdge.bottom))) {
                    curY = point.y;
                    needFixY = YES;
                }
                else {}
                if (needFixY) {
                    CGFloat height = btn.height;
                    if (height!=0) {
                        curY = (NSInteger)curY % (NSInteger)height;
                        point.y = curY;
                    }
                }
            }
        }
        
        return objc_msgSendSuperCasted(&superclass, _cmd, point, event);
    }
    
    创建动态类以及添加方法

    有关的参考内容:
    参考
    https://www.jianshu.com/writer#/notebooks/50287346/notes/88274270/preview

    相关文章

      网友评论

          本文标题:iOS 中的KVO 的原理以及应用

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