- (void)setValue:(id)value forKeyPath:(NSString *)keyPath; // 通过KeyPath来设值
- (void)setValue:(id)value forKey:(NSString *)key; // 通过Key来设值
- (id)valueForKeyPath:(NSString *)keyPath; // 通过KeyPath来取值
- (id)valueForKey:(NSString *)key; // 直接通过Key来取值
key:可以认为是一个相对路劲
keyPath:可以认为是一个绝对路劲
3. 原理
setValue:forKey:
原理
valueForKey:
原理
+ (BOOL)accessInstanceVariablesDirectly;
在 setKey和_setKey 都不存在的情况下会查看此方法。 默认返回 YES
返回 NO,则抛出异常 NSUnknownKwyException
返回 YES,则可以查找成员变量
_key
key
_isKey
isKey
,若成员变量也找不到,抛出异常 NSUnknownKwyException
1. 什么是 KVO
KVO:Key-Value Observing
功能:“键值监听”,可以用于监听某个对象属性值
2. 使用
KVO 使用非常简单,主要为以下几步
为需要监听的属性添加观察者
添加观察者方法
移除观察者
@interface MJPerson : NSObject
@property (assign, nonatomic) int age;
- (void)test;
@implementation MJPerson
- (void)test {}
@interface ViewController ()
@property (strong, nonatomic) MJPerson *person1;
@property (strong, nonatomic) MJPerson *person2;
@property (nonatomic, assign) int number;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.number = 0;
self.person1 = [[MJPerson alloc] init];
self.person1.age = 1;
self.person2 = [[MJPerson alloc] init];
self.person2.age = 2;
// 给person对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
[self addObserver:self forKeyPath:@"number" options:options context:nil];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 值改变
self.person1.age = 10; // 会触发 KVO
self.person1->_age = 10; // 不会触发 KVO
self.person2.age = 20;
_number = 20; // 不会触发 KVO
self.number = 20; // 会触发 KVO
[self setNumber:20]; // 会触发 KVO
// 移除观察者
- (void)dealloc {
[self.person1 removeObserver:self forKeyPath:@"age"];
[self removeObserver:self forKeyPath:@"number"];
// 当监听对象的属性值发生改变时,就会调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
3. 本质 - 重写
set
方法
Automatic key-value observing is implemented using a technique called
isa-swizzling… When an observer is registered for an attribute of an object the
isa pointer of the observed object is modified, pointing to an intermediate class
rather than at the true class
利用 RuntimeAPI 动态生成一个子类 NSKVONotifying_MJPerson,并且让 instance 对象的 isa 指向这个全新的子类
当修改 instance 对象的属性是,会调用 _NSSet***ValueAndNotify()函数
willChangeValueForKey:
父类原来的 setter
didChangeValueForKey:
内部会触发监听器(Observer)的监听方法(observeValueForKeyPath:* ofObject:* change:* context:*)
观察者观察的是属性,只有遵循 KVO 变更属性值的方式才会执行 KVO 的回调方法。如:
self.number = 20;
[self setNumber:20];
如果赋值没有通过 setter 方法或者 KVC,而是直接修改属性对应的成员变量,是不会触发 KVO 机制,更加不会调用回调方法。如:
_number = 20;
,
self.person1->_age = 10;
(可进行
手动触发
)
所以使用 KVO 机制的前提是遵循 KVO 的属性设置方式来变更属性值。
4. 验证
通过断点调试发现
self.perosn1.isa == NSKVONotifying_MJPerson
self.perosn2.isa == MJPerson
NSLog(@"添加之前的 class 对象:%@", object_getClass(self.person1));
NSLog(@"添加之前的 class 对象:%@", object_getClass(self.person2));
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
NSLog(@"添加之后的 class 对象:%@", object_getClass(self.person1));
NSLog(@"添加之后的 class 对象:%@", object_getClass(self.person2));
证明 NSKVONotifying_MJPerson 中的方法(重写的方法)
- (void)printMethodNamesOfClass:(Class)cls {
unsigned int count;
// 获得方法数组
Method *methodList = class_copyMethodList(cls, &count);
// 存储方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍历所有的方法
for (int i = 0; i < count; i++) {
// 获得方法
Method method = methodList[i];
// 获得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
// 释放
free(methodList);
// 打印方法名
NSLog(@"%@ %@", cls, methodNames);
[self printMethodNamesOfClass:object_getClass(self.person1)];
[self printMethodNamesOfClass:object_getClass(self.person2)];
重写 setAge
, class
, dealloc
, _isKVOA
@interface NSKVONotifying_MJPerson : MJPerson
@implementation NSKVONotifying_MJPerson
- (void)setAge:(int)age {
_NSSetIntValueAndNotify();
void _NSSetIntValueAndNotify() {
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
- (void)didChangeValueForKey:(NSString *)key {
[observer observeValueForKeyPath:key ofObject:self change:nil context:nil];
5. 手动触发
如果需要手动触发 KVO, 则需要调用 willChangeValueForKey:
和 didChangeValueForKey:
[self.person1 willChangeValueForKey:@"age"];
self.person1.->_age = 20;
[self.person1 didChangeValueForKey:@"age"];
个人学习记录。
好记性不如烂笔头,有关内容来自小马哥学习视频。
Brick Mover @ 杭州