iOS 中的KVO 的原理以及应用

2021-06-11  本文已影响0人  helinyu

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

上一篇 下一篇

猜你喜欢

热点阅读