美文网首页
OC语言-KVO原理

OC语言-KVO原理

作者: 亲爱的大倩倩 | 来源:发表于2019-06-26 16:21 被阅读0次

键-值观察是一种机制,允许将对象更更改通知其它对象的特定属性
项目中有一个Person类,类里有个name属性

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end

1.添加观察
@property (nonatomic, strong) Person *p;
self.p = [[Person alloc] init];
/*addObserver  观察者
forKeyPath   被监听的属性值
options      监听改变前的值还是改变后的值
context      可以传参数值
为myPeople类对象添加一个观察者,监听"name"属性的变化**/
[self.p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];

2.监听
/*利用KVO监听到对象的属性值发生改变时,调用下面方法
keyPath  哪个属性被改了
object   哪个对象的属性被改了
change   改成了什么样子
context  添加监听时context的参数值**/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
    NSLog(@"keyPath是%@,object是%@,change是%@,context是%@",keyPath,object,change,context);
}

3.更改
/*对象属性值改变,两种改变方式**/
1.self.p.name = @"111";
2.[self.p setValue:@"111" forKey:@"name"];

4.移除
- (void)delloc{
    [self.p removeObserver:self forKeyPath:@"name" context:nil];
}

原理
1.只有setter方法能够进行观察
2.是对动态生成的子类进行的监听,NSKVONotifying_Person是Person类的动态子类

1.只有setter方法能够进行观察,所以KVO的本质是重写了Set方法
@interface Person : NSObject{
    @public
    NSString *body;
}

1.添加监听,但是当值改变时,并不会被监听到
 self.p = [[Person alloc] init];
 [self.p addObserver:self forKeyPath:@"body" options:NSKeyValueObservingOptionNew context:NULL];
self.p->body = @"111";
2.是对动态生成的子类进行的监听,NSKVONotifying_Person是Person类的动态子类

当类对象初始化后,打印类名为Person类
当添加观察者之后,打印类名为NSKVONotifying_Person类
我们可以通过classList方法打印子类会发现, NSKVONotifying_Person为Person的子类


//获取指定类的子类方法
#include <objc/runtime.h>
- (NSArray *)findSubClass:(Class)defaultClass{
    //注册类的总数
    int count = objc_getClassList(NULL,0);
    //创建一个数组,其中包含给定对象
    NSMutableArray * array = [NSMutableArray arrayWithObject:defaultClass];
    //获取所有已注册的类
    Class *classes = (Class *)malloc(sizeof(Class) * count);
    objc_getClassList(classes, count);
    //遍历
    for (int i = 0; i < count; i++) {
        if (defaultClass == class_getSuperclass(classes[i])) {
            [array addObject:classes[I]];
        }
    }
    free(classes);
    return array;
}

通过阅读源码可以看到,KVO是NSObject的一个分类,我们可以仿写一个
首先捋一下思路
1.先判断是否有set方法,如果没有抛出异常,如果有则2
2.动态生成子类
创建类并添加set方法,改变isa指向
3.消息转发

#import <Foundation/Foundation.h>

@interface NSObject (KCKVO)

/**
 KCKVO 添加观察
 @param observer 观察者
 @param keyPath 观察的键
 */
- (void)kc_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
#import "NSObject+KCKVO.h"
#import <objc/message.h>

static NSString *const kKCKVOPrefix = @"KCKOV_";

@implementation NSObject (KCKVO)

/**
observer 观察者 keyPath 观察的键
 */
- (void)kc_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    
    //1判断是否存在于 keyPath是否存在 利用setter方法验证
    // setName
    SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));//NSSelectorFromString 动态加载实例方法。
    Class superClass = object_getClass(self);
    Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);//class_getInstanceMethod 得到类的实例方法
    if (!setterMethod) {//如果没有则抛出异常
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%@:not this setter method",self] userInfo:nil];
    }
    //2:动态创建类 CCKVO_A
    NSString * superClassName = NSStringFromClass(superClass);
    Class newClass;
    if (![superClassName hasPrefix:kKCKVOPrefix]) {
        //创建类 并替换父类
        newClass = [self creatClassFromSuperName:superClassName];
        object_setClass(self, newClass);
    }
    
    //3:添加setter方法  注意这个时候的self==>>>子类
    if (![self hasSeletor:setterSeletor]) {
        const char *types = method_getTypeEncoding(setterMethod);
        class_addMethod(newClass, setterSeletor, (IMP)CCKV0_Setter, types);
    }
    
}

/**
 通过父类创建子类
 */
- (Class)creatClassFromSuperName:(NSString *)superName{
    
    //创建类
    /**
     动态创建类
     1:父类对象
     2:新类的名字
     3:为新类开辟的内存空间
     */
    Class superClass = NSClassFromString(superName);
    NSString *newClassName = [kKCKVOPrefix stringByAppendingString:superName];
    Class newClass = NSClassFromString(newClassName);
    if (newClass) { return newClass;}
    newClass = objc_allocateClassPair(superClass, newClassName.UTF8String, 0);
    
    //添加class方法
    //获取监听对象的class方法 取代class的实现方法---重写
    /**
     为一个类添加方法
     1:类
     2:响应的函数,方法名
     3:指针IMP 即 implementation ,表示由编译器生成的、指向实现方法的指针。也就是说,这个指针指向的方法就是我们要添加的方法
     4:类型:代表一个函数的返回值,参数
     */
    Method classMethod = class_getClassMethod(superClass, @selector(class));
    const char *types = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, @selector(class), (IMP)CCKVO_Class, types);
    //注册类
    objc_registerClassPair(newClass);
    return newClass;
}

/**
 判断是否存在该方法
 */
- (BOOL)hasSeletor:(SEL)selector{
    
    Class observedClass = object_getClass(self);
    unsigned int methodCount = 0;
    //得到一堆方法的名字列表  //class_copyIvarList 实例变量  //class_copyPropertyList 得到所有属性名字
    Method *methodList = class_copyMethodList(observedClass, &methodCount);
    
    for (int i = 0; i<methodCount; i++) {
        SEL sel = method_getName(methodList[i]);
        if (selector == sel) {
            free(methodList);
            return YES;
        }
    }
    free(methodList);
    return NO;
}



#pragma mark - 函数区域(static修饰函数用于限制函数只能在当前文件中调用)

#pragma mark - 从get方法获取set方法的名称 name ===>>> setName:
static NSString  * setterForGetter(NSString *getter){
    if (getter.length <= 0) { return nil; }
    NSString *firstString = [[getter substringToIndex:1] uppercaseString];
    NSString *leaveString = [getter substringFromIndex:1];
    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}

#pragma mark - 从set方法获取getter方法的名称 setName:===> name
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];
    getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
    
    return getter;
}

#pragma mark - CCKVO_Class 新类class所指向的函数实现
static Class CCKVO_Class(id self){
    return class_getSuperclass(object_getClass(self));
}

//_cmd在Objective-C的方法中表示当前方法的selector,正如同self表示当前方法调用的对象实例。
#pragma mark - 子类添加的setter方法
static void CCKV0_Setter(id self,SEL _cmd,id newValue){
    
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = getterForSetter(setterName);
    if (!getterName) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%@ not instance getter",self] userInfo:nil];
        return;
    }
    
    id oldValue = [self valueForKey:getterName];
    [self willChangeValueForKey:getterName];
    //消息转发
    void (*KC_OBJCSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
    struct objc_super KCSuperSt = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    KC_OBJCSendSuper(&KCSuperSt,_cmd,newValue);
    [self didChangeValueForKey:getterName];
    
    NSLog(@"%@",newValue);
}

@end

相关文章

网友评论

      本文标题:OC语言-KVO原理

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