CC的博客

  • 首页
  • iOS
  • Android
  • React-Native
  • 读书杂谈
  • About
CC
记录美好生活
  1. 首页
  2. 技术编程
  3. iOS
  4. 正文

KVC本质探索

2021/03/23

简介

KVC全称是Key-Value Coding,键值编码,可以通过Key来访问和修改Value。
常见API

- (void)setValue:(id)value forKey:(NSString *)key;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;

setValue:forKey:根据属性名做赋值的操作

setValue: forKeyPath:根据key的路径做多层级间的赋值操作

使用示例代码:

    People *people = [People new];
    Dog *dog = [Dog new];
    people.dog = dog;
    [people setValue:@10 forKey:@"age"];
    [people setValue:@"s" forKeyPath:@"dog.name"];

    [people valueForKey:@"age"];

    [people valueForKeyPath:@"dog.name"];

底层原理

赋值

当我们调用setValue:forKeyPath:方法后,系统会按照顺序查找两个方法:setKey和_setKey,如果实现了这两个方法其中一个,那么系统就会调用对应的方法。

通过在People类中分别实现setAge和_setAge两个方法,做实验代码:

People.h

#import <Foundation/Foundation.h>

@class Dog;
@interface People : NSObject

@property(nonatomic,copy) NSString *name;

@property (nonatomic, strong) Dog *dog;

@end

People.m

@implementation People

- (void)setAge:(NSInteger)age {
    NSLog(@"setAge");
}
- (void)_setAge:(NSInteger)age {
    NSLog(@"_setAge");
}
- (NSInteger)age {
    NSLog(@"get age");
    return 0;
}
@end

实验结果:

只保留setAge方法,会输出setAge;只保留_setAge方法,会输出_setAge;当两个方法同时存在时,setAge方法优先于_setAge方法。

如果没有实现setKey和_setKey这两个方法的话,会继续调用accessInstanceVariablesDirectly方法,这个方法的返回值是BOOL类型(默认返回值为YES);如果返回值是NO,系统会调用setValue:forUndefinedKey:方法,并抛出NSUnknownKeyException类型的异常。如果返回值是YES,那么会继续按照顺序依次查找 _key、_isKey、key、isKey这四个成员变量,如果找到其中一个成员变量,完成赋值。如果四个成员变量全都未找到,系统会调用setValue:forUndefinedKey:方法,并抛出NSUnknownKeyException类型的异常。

继续在People类中实验没有实现setKey和_setKey方法,但存在_isKey成员变量的情况,代码如下:

#import "People.h"


@interface People ()

{
    NSInteger _isAge;
}
@end

@implementation People

- (NSInteger)age {
    NSLog(@"%ld",_isAge);
    NSLog(@"get age");
    return 0;
}

+ (BOOL)accessInstanceVariablesDirectly {
    return YES;
}
@end

能正确给People实例对象赋值,系统没有抛出异常。

取值

当我们调用valueforKey:方法后,系统会按照顺序查找以下方法:getKey、key、isKey、_key,如果实现了其中一个,那么系统就会调用对应的方法。

通过在People类中分别实现getKey、key、isKey、_key方法,做实验代码:

People.m

#import "People.h"
@implementation People

- (NSInteger)getAge {
    NSLog(@"getAge");
    return 10;
}

- (NSInteger)age {
    NSLog(@"age");
    return 10;
}

- (NSInteger)isAge {
    NSLog(@"isAge");
    return 10;
}

- (NSInteger)_Age {
    NSLog(@"_Age");
    return 10;
}

- (void)setAge:(NSInteger)age {
    NSLog(@"setAge");
}

@end

实验结果:
getKey、key、isKey、_key方法优先级依次降低。

如果没有实现getKey、key、isKey、_key这几个方法的话,会继续调用accessInstanceVariablesDirectly方法,这个方法的返回值是BOOL类型(默认返回值为YES);如果返回值是NO,系统会调用valueForUndefinedKey:方法,并抛出NSUnknownKeyException类型的异常。如果返回值是YES,那么会继续按照顺序依次查找 _key、_isKey、key、isKey这四个成员变量,如果找到其中一个成员变量,完成取值。如果四个成员变量全都未找到,系统会调用valueForUndefinedKey:方法,并抛出NSUnknownKeyException类型的异常。

KVC赋值能触发KVO么?

可以,KVC赋值的时候,应该有调用
willChangeValueForKey:
didChangeValueForKey:通知KVO监听者值改变。

假设定义一个Student类,对agekey进行KVO监听和KVC赋值操作的关键函数进行重写打印log。

@interface Student : NSObject

@end

@implementation Student

- (void)setAge:(int)age {
    NSLog(@"set age");
}

- (int)age {
    NSLog(@"age");
    return 0;
}

-(void)setValue:(id)value forKey:(NSString *)key {

    NSLog(@"setvalue forkey");
    [super setValue:value forKey:key];
}

- (id)valueForKey:(NSString *)key {
    NSLog(@"value forkey");
    return [super valueForKey:key];
}

- (void)willChangeValueForKey:(NSString *)key {
    NSLog(@"willChangeValueForKey");
    [super willChangeValueForKey:key];
}

- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"didChangeValueForKey begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey end");
}

@end

@interface StudentObserver : NSObject

@end

@implementation StudentObserver


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"observeValuechange %@",change);
}
@end


当我们Student的实例对象的age属性进行监听新旧值的改变,然后用KVC的方式对age赋值,观察输出结果。

    Student *stu = [[Student alloc] init];
    StudentObserver *ob = [[StudentObserver alloc] init];
    [stu addObserver:ob forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"111"];

    [stu setValue:@1 forKey:@"age"];

输出结果如下:

setvalue forkey
willChangeValueForKey
value forkey
age
set age
didChangeValueForKey begin
value forkey
age
observeValuechange {
    kind = 1;
    new = 0;
    old = 0;
}
didChangeValueForKey end

观察输出log,可以发现setValue:forKey:方法内部,先调用了willChangeValueForKey,然后调用了valueForKey:获取旧值,紧接着对age进行赋值操作,然后调用了didChangeValueForKey方法内部开始又调用了valueForKey:获取新值,方法最后通知监听改变。

标签: 暂无
最后更新:2021/03/23

CC

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

COPYRIGHT © 2020 CC的博客. ALL RIGHTS RESERVED.

Theme Kratos

豫ICP备2023032048号