2.KVC-KVO基本使用及底层探究

2020-07-08  本文已影响0人  那抹浮沉

基础使用

KVC的使用

KVO的使用

底层探究

异常情况

其他如NSArray,NSSet,NSNumber,有兴趣的自行研究,调用方法有所不同

KVC正确性的验证,有兴趣的同上

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

基础使用

- (void)easyValuation
{
    Major *major = [[Major alloc]init];
    //常规赋值
    major.heig = @"1.98";
    //KVC赋值
    [major setValue:@"0.98" forKey:@"heig"];
    [major setValue:@"1.88" forKeyPath:@"heig"];
    //另外通过KVC可以进行自动类型转换,age是int类型,传入的依旧可以是字符串
    [major setValue:@"18" forKey:@"age"];
    [major setValue:@"28" forKeyPath:@"age"];
    
    NSLog(@"%@",major.heig);
}
- (void)complexValuation
{
    //forKeyPath 包含forKey 的功能,所以使用forKeyPath 就行
    //forKeyPath 可以进行内部点语法
    //属性一定要有,不然会崩溃
    //Student类中有一个Major类的属性
    Student * stud = [[Student alloc]init];
    stud.major.age = 22;
    //KVC赋值的3中方式
    [stud.major setValue:@"33" forKeyPath:@"age"];
    [stud.major setValue:@"33" forKey:@"age"];
    [stud setValue:@"33" forKeyPath:@"major.age"];
    
    NSLog(@"%d",stud.major.age);
}
- (void)modifiPrivatelyVariable
{
    //修改类的私有成员变量
    Major *major = [[Major alloc]init];
    //Major类中有一个私有属性MajorName,强行赋值
    [major setValue:@"33" forKey:@"MajorName"];
    NSLog(@"%@",major);
}
- (void)transformModelAndDic
{
    Major *major = [[Major alloc]init];
    // 字段快速赋值对应的属性(适用于简单的字典转模型)  不建议使用
    //原因1:字典中的的key,在类的属性列表中必须有(可以不使用),不然会崩溃
    //2.如果模型中带有模型(如字典中嵌套数组或字典),子模型则赋值不成功
    NSDictionary *dic = @{@"heig":@"188",
                          @"age":@18};
    //字典转模型
    [major setValuesForKeysWithDictionary:dic];
    //模型转字典
    NSDictionary * dic2 = [major dictionaryWithValuesForKeys:@[@"heig",@"age"]];

    //关于字典转模型,MJExtension,YYModel 等优秀第三方框架了解一下
    //Mantle需要模型类继承Mantle ,   JSONModel需要模型类继承JSONModel
}
//有这么个场景,电话薄的索引要返回一个字符串数组,就可以从模型数组中这么取出
- (void)sdadwx
{
    Major *major1 = [[Major alloc]init];
    major1.heig = @"198";
    
    Major *major2 = [[Major alloc]init];
    major2.heig = @"197";
    
    Major *major3 = [[Major alloc]init];
    major3.heig = @"199";
    
    NSArray *majorAry = @[major1,major2,major3];
    //取出同一属性的值
    NSArray *allHeig = [majorAry valueForKeyPath:@"heig"];
    NSLog(@"%@",allHeig);
}
    //这里抛出一个引子
    //点击屏幕改变按钮颜色,当然也可以访问其私有属性进行设置
    self.btn = [UIButton buttonWithType:UIButtonTypeCustom];
    self.btn.backgroundColor = [UIColor redColor];
    self.btn.frame = CGRectMake(0, 500, 100, 40);
    [self.view addSubview:self.btn];

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.btn setValue:[UIColor blueColor] forKeyPath:@"backgroundColor"];
}

KVO的使用

//注册成为观察者,监听对象的某个属性的值的改变
- (void)regisObsever
{
    [self.student addObserver:self forKeyPath:@"major.MajorName" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    //3秒后改变major.MajorName的值
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.student setValue:@"计算机BBB" forKeyPath:@"major.MajorName"];
    });
}
//监听KeyPath 值的变化
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    if ([keyPath isEqual:@"major.MajorName"]) {
        //获取前后变化的值
        NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
        NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];
        
    }else{
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}
-(void)dealloc
{
    [self.student removeObserver:self forKeyPath:@"major.MajorName"];
}

扩展
KVO 生效
1.使用 setter 方法改变值 ,KVO 生效会生效
2.使用 setValue:forKey 即 KVC 改变值 KVO 也会生效,因为 KVC 会去 调用 setter 方法

底层探究

当某个类的对象第一次被观察时,系统就会在运行时动态的创建对应于该类的一个派生类,在这个派生来中,系统会重写父类中被观察属性的setter方法

//模拟kvo内部代码,实际上在派生类中
- (void)setHeig:(NSString *)heig
{
    //属性改变前调用,通知系统改属性即将发生变化
    [self willChangeValueForKey:@"heig"];
    _heig = heig;
    //属性改变前调用,通知系统改属性已经发生变化
    [self didChangeValueForKey:@"heig"];
}

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    if ([key isEqualToString:@"heig"]) {
        //检测到后,取消变更发送通知的操作
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}

父类会将对象的isa指针指向新创建的派生类,因此,这个类的对象就成了派生类的对象了,之后调用属性的setter方法实际上调用的是重写后的setter方法,从而实现了键值通知机制,此外,派生类还重写了dealloc方法来释放内存资源

1.查找类中是否存在与key对应的 setKey _setKey setIsKey方法,存在直接调用,进行赋值
2.若找不到,查看accessInstanceVariablesDirectly方法返回值,默认是YES,开启间接访问成员变量,为NO则为不允许
3.如果为NO,就会调用 setValue: forUndefinedKey:方法,并抛出一个异常,如有需要可以重写这个方法
4.如果为YES,继续寻找成员变量_key _isKey key isKey
5.若未找到,就会调用 setValue: forUndefinedKey:方法,并抛出一个异常,如有需要可以重写这个方法
6.若找到,找到即停,进行赋值,取值的时候,找对应的get方法,比如找到_key,并赋值,
取值就会调用_key方法取值(只有get方法中_key是有值的),而不是从最开始的get方法 getKey 中取值

1.查找类中是否存在与key对应的 getKey key isKey _key 依次调用这些get方法,找到的话直接调用,
2.若找不到,查看accessInstanceVariablesDirectly 方法返回值,默认是YES,开启间接访问成员变量,为NO则为不允许
3.如果为NO,就会调用 valueForUndefinedKey:方法,并抛出一个异常,如有需要可以重写这个方法
4.如果为YES,继续寻找成员变量_key _isKey key isKey
5.若未找到,就会调用 valueForUndefinedKey:方法,并抛出一个异常,如有需要可以重写这个方法
6.若找到,找到即停,进行取值的

#import <Foundation/Foundation.h>
@interface Student : NSObject
{
    @public
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;
}
@property (nonatomic, strong)Major *major;
@end
#import "Student.h"
@implementation Student
//苹果默认you实现
//set相关
- (void)setName:(NSString *)name
{
    NSLog(@"%s",__func__);
}
- (void)_setName:(NSString *)name
{
    NSLog(@"%s",__func__);
}
- (void)_setIsName:(NSString *)isName
{
    NSLog(@"%s",__func__);
}
//get 相关
- (NSString *)getName{
    NSLog(@"%s",__func__);//[Student getName]
    //将SEL对象转为NSString对象
    NSLog(@"%@",NSStringFromSelector(_cmd));//_cmd代表当前方法,输出转化的字符串:getName
    return NSStringFromSelector(_cmd);
}
- (NSString *)name{
    NSLog(@"%s",__func__);
    return NSStringFromSelector(_cmd);
}
- (NSString *)isName{
    NSLog(@"%s",__func__);
    return NSStringFromSelector(_cmd);
}
- (NSString *)_name{
    NSLog(@"%s",__func__);
    return NSStringFromSelector(_cmd);
}

//是否开启间接访问,默认返回yes
//如果关闭就不会找 _key  _isKey  key isKey这些成员变量
+ (BOOL)accessInstanceVariablesDirectly
{
    return YES;
}
@end

由于赋值先赋值的是 _name,所以,下面输出是 _name,而不是name
NSLog(@"男男女女女女%@",[self.student valueForKeyPath:@"name"]);

异常情况

上一篇下一篇

猜你喜欢

热点阅读