iOS底层原理 - 探寻KVC本质

面试题引发的思考:

Q: KVC的赋值和取值过程是怎样的?原理是什么?
Q: 通过KVC修改属性会触发KVO么?

1. 何为KVC?

KVC的全称是Key-Value Coding,即“键值编码”,可以通过一个key来访问某个属性。
常见API:
- (void)setValue:(id)value forKey:(NSString *)key;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;

使用方法如下:

// TODO: -----------------  Cat类  -----------------
@interface Cat : NSObject
@property (nonatomic, copy) NSString *name;
@end

// TODO: -----------------  Person类  -----------------
@interface Person : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, strong) Cat *cat;
@end

// TODO: -----------------  ViewController类  -----------------
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    Person *person = [[Person alloc] init];
    
    [person setValue:@10 forKey:@"age"];
    NSLog(@"age - %@", [person valueForKey:@"age"]);
    
    person.cat = [[Cat alloc] init];
    [person setValue:@"miaomiao" forKeyPath:@"cat.name"];
    NSLog(@"name - %@", [person valueForKeyPath:@"cat.name"]);
}
打印结果

2. KVC赋值原理是什么?

iOS底层原理 - 探寻KVC本质_第1张图片
setValue: forKey: 原理

KVC赋值过程如上图,验证方法如下:

// TODO: -----------------  Person类  -----------------
@interface Person : NSObject {
    @public
    // 按照`_key`、`_isKey`、`key`、`isKey`顺序查找成员变量
    int _age;
    int _isAge;
    int age;
    int isAge;
}
@end

@implementation Person
// 优先调用`setAge:`方法
- (void)setAge:(int)age {
    NSLog(@"setAge - %d", age);
}
// 如果`setAge:`方法不存在,则调用`_setAge:`方法
- (void)_setAge:(int)age {
    NSLog(@"_setAge - %d", age);
}
// 如果`setAge:`、`_setAge:`方法都不存在,则调用`accessInstanceVariablesDirectly`方法,查找成员变量
+ (BOOL)accessInstanceVariablesDirectly {
    return YES;
}
@end

// TODO: -----------------  ViewController类  -----------------
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    Person *person = [[Person alloc] init];
    // 通过KVC修改age属性
    [person setValue:@10 forKey:@"age"];
}

通过KVC修改age属性时:

  • 优先调用setAge:方法
  • 如果setAge:方法不存在,则调用_setAge:方法
  • 如果setAge:_setAge:方法都不存在,则调用accessInstanceVariablesDirectly方法,返回值为YES时,按照_key_isKeykeyisKey顺序查找成员变量
iOS底层原理 - 探寻KVC本质_第2张图片
成员变量顺序查找

以上结论皆可通过注释代码、断点得到,此文不做详细介绍。

3. KVC取值原理是什么?

iOS底层原理 - 探寻KVC本质_第3张图片
valueForKey: 原理

KVC取值过程如上图,验证方法如下:

// TODO: -----------------  ViewController类  -----------------
@interface Person : NSObject {
    @public
    // 按照`_key`、`_isKey`、`key`、`isKey`顺序查找成员变量
    int _age;
    int _isAge;
    int age;
    int isAge;
}
@end

@implementation Person
// 按照`getKey`、`key`、`isKey`、`_key`顺序查找方法
- (int)getAge {
    return 11;
}
- (int)age {
    return 12;
}
- (int)isAge {
    return 13;
}
- (int)_age {
    return 14;
}
// 如果以上方法都不存在,则调用`accessInstanceVariablesDirectly`方法,查找成员变量
+ (BOOL)accessInstanceVariablesDirectly {
    return YES;
}
@end

// TODO: -----------------  ViewController类  -----------------
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    Person *person = [[Person alloc] init];
    person->_age = 11;
    person->_isAge = 12;
    person->age = 13;
    person->isAge = 14;
    NSLog(@"%@", [person valueForKey:@"age"]);
}

通过KVC获取age属性时:

  • 优先按照getKeykeyisKey_key顺序查找方法
  • 如果以上方法方法都不存在,则调用accessInstanceVariablesDirectly方法,返回值为YES时,按照_key_isKeykeyisKey顺序查找成员变量

以上结论皆可通过注释代码、断点得到,此文不做详细介绍。

4. 验证:通过KVC修改属性会触发KVO?

// TODO: -----------------  Person类  -----------------
@interface Person : NSObject {
    @public
    int _age;
}
@end

@implementation Person
+ (BOOL)accessInstanceVariablesDirectly {
    return YES;
}
- (void)willChangeValueForKey:(NSString *)key {
    NSLog(@"willChangeValueForKey - begin");
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey - end");
}
- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"didChangeValueForKey - begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey - end");
}
@end

// TODO: -----------------  ViewController类  -----------------
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    Person *person = [[Person alloc] init];
    // 添加KVO监听
    [person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld |NSKeyValueObservingOptionNew context:NULL];
    // 通过KVC修改age的属性
    [person setValue:@10 forKey:@"age"];
    // 移除KVO监听
    [person removeObserver:self forKeyPath:@"age"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"observeValueForKeyPath - %@", change);
}
iOS底层原理 - 探寻KVC本质_第4张图片
KVC修改属性打印结果

由打印结果可知:
通过KVC修改age属性,并没有调用setAge:方法,而是直接修改的成员变量_age

但是如此也能触发监听者的observeValueForKeyPath: ofObject: change: context:方法,说明通过KVC修改age属性时,会调用willChangeValueForKey:方法和didChangeValueForKey:方法;

那么可以证明:通过KVC修改属性会触发KVO

你可能感兴趣的:(iOS底层原理 - 探寻KVC本质)