KVO

  • KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变

添加KVO方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
self.person = [[Person alloc] init];

// 给person对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:@"123"];

// observeValueForKeyPath:ofObject:change:context:
// 当监听对象的属性值发生改变时,就会调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}

//移除监听
- (void)dealloc {
[self.person1 removeObserver:self forKeyPath:@"age"];
}

获取类的所有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (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.person)];

//打印结果,即为中间派生类的方法
NSKVONotifying_Person setAge:, class, dealloc, _isKVOA,

KVC

  • KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性
  • 常见的API有
    1
    2
    3
    4
    5
    - (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
    - (void)setValue:(id)value forKey:(NSString *)key;

    - (id)valueForKeyPath:(NSString *)keyPath;
    - (id)valueForKey:(NSString *)key;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface Cat : NSObject
@property (assign, nonatomic) int weight;
@end

@interface Person : NSObject
@property (assign, nonatomic) int age;

@property (assign, nonatomic) Cat *cat;
@end

Person *person = [[Person alloc] init];
// 进行赋值
//[person setValue:[NSNumber numberWithInt:10] forKey:@"age"];
[person setValue:@10 forKey:@"age"];

//通过keyPath,进行赋值
person.cat = [[Cat alloc] init];
[person setValue:@10 forKeyPath:@"cat.weight"];

setValue:forKey:的原理

  • setValue:forKey:
  • 按照setKey: _setKey: 顺序查找方法
  • 找到方法 -> 传递参数,调用方法
  • 没有找到方法 -> 查看 ==accessInstanceVariablesDirectly== 方法的返回值
  • 返回 YES -> 按照 _key、_isKey、key、isKey 顺序查找==成员变量==,直接赋值,如果还是没有找到,走下一步
  • 返回NO -> 调用 ==setValue:forUndefinedKey:== 并抛出异常NSUnknownKeyException
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@interface Person : NSObject
{
@public
// 按钮 _key、_isKey、key、isKey 顺序查找
// int _age;
// int _isAge;
// int age;
// int isAge;
}
@end

@implementation MJPerson
//先找setAge方法,进行赋值
//- (void)setAge:(int)age
//{
// NSLog(@"setAge: - %d", age);
//}

//再找_setAge方法,进行赋值
//- (void)_setAge:(int)age
//{
// NSLog(@"_setAge: - %d", age);
//}

// 默认的返回值就是YES
//+ (BOOL)accessInstanceVariablesDirectly
//{
// return YES;//默认的返回值就是YES
// return NO;//抛出异常NSUnknownKeyException, setValue:forUndefinedKey:
//}

@end

valueForKey:的原理

  • valueForKey:
  • 按照 getKey、key、 isKey、_key 顺序查找方
  • 找到方法 -> 调用方法,返回值
  • 没有找到方法 -> 查看 ==accessInstanceVariablesDirectly== 方法的返回值
  • 返回 YES -> 按照 _key、_isKey、key、isKey 顺序查找成员变量,直接赋值,如果还是没有找到成员变量,走下一步
  • 返回NO -> 调用 调用 ==valueForUndefinedKey:== 并抛出异常NSUnknownKeyException
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@interface Person : NSObject
{
@public
// 按照 _key、_isKey、key、isKey 顺序查找
// int _age;
// int _isAge;
// int age;
// int isAge;
}
@end

@implementation MJPerson
//- (int)getAge
//{
// return 11;
//}

//- (int)age
//{
// return 12;
//}

//- (int)isAge
//{
// return 13;
//}

//- (int)_age
//{
// return 14;
//}

// 默认的返回值就是YES
//+ (BOOL)accessInstanceVariablesDirectly
//{
// return YES;//默认的返回值就是YES
// return NO;//抛出异常NSUnknownKeyException, setValue:forUndefinedKey:
//}

@end

通过KVC修改属性是否会触发KVO?

  • 会触发KVO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Observer *observer = [[Observer alloc] init];
Person *person = [[Person alloc] init];

// 添加KVO监听
[person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];

// 通过KVC修改age属性
[person setValue:@10 forKey:@"age"];

// 也可手动触发KVO
// [person willChangeValueForKey:@"age"];
// person->_age = 10;
// [person didChangeValueForKey:@"age"];

// 移除KVO监听
[person removeObserver:observer forKeyPath:@"age"];

常见问题

本质

  • 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
  • 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
    • willChangeValueForKey:
    • 父类原来的setter
    • didChangeValueForKey:
      • 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)

手动触发KVO

  • 手动调用willChangeValueForKey:和didChangeValueForKey:

直接修改成员变量会触发KVO么?

  • 不会触发KVO

参考