KVC底层实现原理
KVC基本使用
定义DPLPerson和DPLCat类,DPLCat类拥有weight
属性,DPLPerson类拥有age
和cat
属性。
// DPLCat.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface DPLCat : NSObject
@property (nonatomic, assign) int weight;
@end
NS_ASSUME_NONNULL_END
// DPLCat.m
#import "DPLCat.h"
@implementation DPLCat
@end
// DPLPerson.h
#import <Foundation/Foundation.h>
#import "DPLCat.h"
NS_ASSUME_NONNULL_BEGIN
@interface DPLPerson : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, strong) DPLCat *cat;
@end
// DPLPerson.m
NS_ASSUME_NONNULL_END
#import "DPLPerson.h"
@implementation DPLPerson
@end
通过KVC给属性赋值
DPLPerson *person = [[DPLPerson alloc] init];
person.cat = [[DPLCat alloc] init];
// KVC设值
[person setValue:@10 forKey:@"age"];
[person setValue:@3 forKeyPath:@"cat.weight"];
// KVC取值
NSLog(@"person.age --- %@", [person valueForKey:@"age"]);
NSLog(@"person.cat.weight --- %@", [person valueForKeyPath:@"cat.weight"]);
打印
person.age --- 10
person.cat.weight --- 3
以上就是KVC基本使用
KVC设值原理
- 调用
setValue:forKey:
- 按顺序查找方法
setKey:
、_setKey
,如果找到直接执行方法 - 如果没有找到判断
accessInstanceVariablesDirectly
方法返回值,如果返回NO,抛出NSUnknownKeyException
异常 - 如果
accessInstanceVariablesDirectly
方法返回YES,按顺序查找成员变量_key
、_isKey
、key
、isKey
,如果找到,直接为成员变量赋值 - 如果没有找到,抛出
NSUnknownKeyException
异常
代码验证:
step1
设置属性值,系统自动生成set
方法,重写set
方法
- (void)setAge:(int)age {
_age = age;
NSLog(@"setAge: --- age");
}
执行程序,打印
setAge: --- age
step2
删除设置age
属性语句,增加_age
成员变量。删除重写的set
方法,增加_set
方法
//@property (nonatomic, assign) int age;
@public
int _age;
//- (void)setAge:(int)age {
// _age = age;
//
// NSLog(@"setAge: --- age");
//}
- (void)setAge:(int)age {
_age = age;
NSLog(@"_setAge --- age");
}
执行程序,打印
_setAge --- age
step3
将set
和_set
方法都删除,实现accessInstanceVariablesDirectly
返回NO
//- (void)setAge:(int)age {
// _age = age;
//
// NSLog(@"setAge: --- age");
//}
//- (void)_setAge:(int)age {
// _age = age;
//
// NSLog(@"_setAge --- age");
//}
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
执行程序,系统抛出异常
Terminating app due to uncaught exception 'NSUnknownKeyException'
step4
accessInstanceVariablesDirectly
返回YES
,依次验证_key
、_isKey
、key
、isKey
@public
// int _age;
// int _isAge;
// int age;
int isAge;
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
正常打印
person.age --- 10
step5
删除所有成员变量
@public
// int _age;
// int _isAge;
// int age;
// int isAge;
执行程序,系统抛出异常
Terminating app due to uncaught exception 'NSUnknownKeyException'
KVC取值原理
- 调用
valueForKey:
方法 - 按顺序查找方法
getKey:
、key
、isKey
、_key
,如果找到,直接调用方法取值 - 如果没有找到,判断
accessInstanceVariablesDirectly
方法返回值,如果返回NO
,抛出异常NSUnknownKeyException
- 如果找到,按顺序查找成员变量
_key
、_isKey
、key
、isKey
,如果找到,直接取值 - 如果没找到,抛出异常
NSUnknownKeyException
代码验证
step1
删除所有成员变量和属性,实现方法getAge
- (int)getAge {
NSLog(@"getAge");
return 100;
}
执行程序,打印
getAge
person.age --- 100
按照异常步骤,依次验证key
、isKey
、_key
方法
//- (int)getAge {
// NSLog(@"getAge");
//
// return 100;
//}
//- (int)age {
// NSLog(@"age");
//
// return 101;
//}
//- (int)isAge {
// NSLog(@"isAge");
//
// return 102;
//}
- (int)_age {
NSLog(@"_age");
return 103;
}
step2
增加成员变量,为成员变量赋值,实现accessInstanceVariablesDirectly
方法,返回NO
@public
int _age;
int _isAge;
int age;
int isAge;
DPLPerson *person = [[DPLPerson alloc] init];
person->_age = 100;
person->_isAge = 101;
person->age = 102;
person->isAge = 103;
NSLog(@"person.age --- %@", [person valueForKey:@"age"]);
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
执行程序,抛出异常
NSUnknownKeyException
step3
实现accessInstanceVariablesDirectly
方法,返回NO
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
执行程序,打印
person.age --- 100
step4
注释掉_age
@public
// int _age;
int _isAge;
int age;
int isAge;
DPLPerson *person = [[DPLPerson alloc] init];
// person->_age = 100;
person->_isAge = 101;
person->age = 102;
person->isAge = 103;
NSLog(@"person.age --- %@", [person valueForKey:@"age"]);
执行程序,打印
person.age --- 101
按以上步骤,依次验证age
和isAge
step5
删除所有成员变量
@public
// int _age;
// int _isAge;
// int age;
// int isAge;
执行程序,抛出异常
NSUnknownKeyException
KVC与KVO的关系
KVC和KVO名字看上去就很像,通过KVC修改属性值是否会触发KVO呢?
答案是肯定的,通过KVC修改属性值确实会触发KVO!
我们为DPLPerson类增加成员变量_age
,并未age
增加KVO,通过KVC修改_age
的值,观察是否会触发KVC监听。
@interface DPLPerson : NSObject
{
@public
int _age;
}
DPLPerson *person = [[DPLPerson alloc] init];
person->_age = 10;
[person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
[person setValue:@20 forKey:@"age"];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"监听到%@的%@发生了改变 --- change:%@", object, keyPath, change);
}
执行程序,打印
监听到<DPLPerson: 0x60000150ef50>的age发生了改变 --- change:{
kind = 1;
new = 20;
old = 10;
}
直接修改成员变量值,是不会触发KVO;但是,使用KVC修改成员变量值,就会触发KVO。
在之前的文章《KVO底层实现原理》中已经说过,手动出发KVO需要手动执行两个方法willChangeValueForKey:
和didChangeValueForKey:
,KVO在修改值时,就会调用这两个方法,所有会触发KVO。
重写DPLPerson类的willChangeValueForKey:
和didChangeValueForKey:
方法
@implementation DPLPerson
- (void)willChangeValueForKey:(NSString *)key {
NSLog(@"willChangeValueForKey");
[super willChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"didChangeValueForKey");
[super didChangeValueForKey:key];
}
@end
执行程序,打印
willChangeValueForKey
didChangeValueForKey
监听到<DPLPerson: 0x6000024c5d40>的age发生了改变 --- change:{
kind = 1;
new = 20;
old = 10;
}
可以看到,确实调用了这两个方法。