美文网首页
KVC内部原理?KVC和KVO关系?

KVC内部原理?KVC和KVO关系?

作者: 再好一点点 | 来源:发表于2021-10-20 11:10 被阅读0次
KVC都不陌生,多多少少都用过,那么KVC内部原理是怎样的?KVC和KVO什么关系?使用KVC赋值会触发KVO吗?

先看两张图

setValue:forKey:的原理
kvc赋值
valueForKey:的原理
kvc取值
Person类
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end

@implementation Person
@end
Observer类
@interface Observer : NSObject
@end

@implementation Observer

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

@end

一. 使用KVC赋值

main函数
#import "Person.h"
#import "Observer.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        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:@1 forKey:@"age"];
        // 移除KVO监听
        [person removeObserver:observer forKeyPath:@"age"];

    }
    return 0;
}

运行上述程序,结果为:

 observeValueForKeyPath - {
    kind = 1;
    new = 1;
    old = 0;
}

很显然,使用KVC修改属性的值会触发KVO。那么内部实现原理是什么样的呢?接下来一起探究一下。

1. Person.m里边添加age的set方法
@implementation Person
- (void)setAge:(int)age
{
    _age = age;
    NSLog(@"setAge: - %d", age);
}
@end

再次运行程序,结果如下:

 setAge: - 1
 observeValueForKeyPath - {
    kind = 1;
    new = 1;
    old = 0;
}

发现调用了age的set方法

2. 再次修改Person类,为了不让系统自动生成setAge,所以不再使用属性age了,而是使用成员变量_age。
@interface Person : NSObject{
    @public
    int _age;
}
@end

- (void)_setAge:(int)age
{
    NSLog(@"_setAge: - %d", age);
}

结果如下:

 _setAge: - 1
 observeValueForKeyPath - {
    kind = 1;
    new = 0;
    old = 0;
}

发现居然调用了_setAge。

3. Person.m添加方法
+ (BOOL)accessInstanceVariablesDirectly {
    return NO;
}

运行程序直接崩溃,报错如下:

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Person 0x100564070> valueForUndefinedKey:]: this class is not key value coding-compliant for the key age.'

这个方法就是询问如果没有找到指定的方法,是否允许直接查找对应的成员变量,如果返回NO表示不允许查找,所以崩溃了。此方法默认会返回YES。

4. Person类修改如下:
@interface Person : NSObject{
    @public
    int isAge;
    int _age;
    int age;
    int _isAge;
}
@end

@implementation Person
@end

运行程序,main函数里边在[person setValue:@1 forKey:@"age"];下面打断点,查看控制台如下

含四个成员变量

可以看到,_age时被赋值了

5. 如果此时删除掉_age这个变量会怎么样呢?

结果如下:

移除_age成员变量
从图中可以看出,此时调用[person setValue:@1 forKey:@"age"];赋值给了_isAge这个成员边变量。
6. 如果继续删除掉_isAge,那么会赋值给age。
7. 如果继续删除掉age,那么会赋值给isAge。
8. Person实现如下方法:
- (void)willChangeValueForKey:(NSString *)key
{
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey - %@", key);
}

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

运行结果如下:

willChangeValueForKey - age
didChangeValueForKey - begin - age
 observeValueForKeyPath - {
    kind = 1;
    new = 1;
    old = 0;
}
didChangeValueForKey - end - age

调用了willChangeValueForKeydidChangeValueForKey,所以就会收到KVO的消息通知。

使用KVC赋值总结如下:

使用KVC给某一变量赋值,方法以及成员变量查找顺序为:
首先查找setKey方法,如果找不到该方法就调用_setKey方法,如果还没找到对应的方法就调用+ (BOOL)accessInstanceVariablesDirectly方法,看是否返回YES(默认返回YES),如果返回NO,直接抛出异常,如果返回YES,则查找成员变量,顺序依次是_key_isKeykeyisKey。在上述方法查找或者变量查找过程中如果找到直接赋值,在赋值完成以后会调用willChangeValueForKeydidChangeValueForKey,此时就能收到观察者的消息通知。如果没找到调用- (void)setValue:(id)value forUndefinedKey:(NSString *)key,然后抛出异常。
直接查看最上方的图片逻辑更加清晰。

二. 使用KVC取值

1.Person含有以下四个方法

Person类如下:

@interface Person : NSObject
{
    @public
    int age;
    int isAge;
    int _isAge;
    int _age;
}
@end

@implementation Person

- (int)getAge
{
    return 11;
}

- (int)age
{
    return 12;
}

- (int)isAge
{
    return 13;
}

- (int)_age
{
    return 14;
}
@end

main函数如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        NSLog(@"%@", [person valueForKey:@"age"]);
    }
    return 0;
}

运行结果为:

 11

可见调用的是getAge方法。

2.Person移除getAge方法

再次运行结果为:

 12
3.Person移除age方法

再次运行结果为:

 13
4.Person移除isAge方法

再次运行结果为:

 14
4.Person移除所有方法,Main修改如下:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person->_age = 10;
        person->_isAge = 11;
        person->age = 12;
        person->isAge = 13;        
        NSLog(@"%@", [person valueForKey:@"age"]);
    }
    return 0;
}

运行结果为:

 10
5.移除person->_age = 10;int _age;

运行结果为:

 11
6.移除person->_isAge = 11;int _isAge;

运行结果为:

 12
7.移除person->age = 12;int age;

运行结果为:

 13
KVC取值总结如下:

首先查找方法getKey,找到直接返回结果,没找到就找方法key,如果还是找不到就找方法isKey,如果还是找不到就找方法_key。如果还是找不到就查找成员变量顺序依次是_key_isKeykeyisKey。找到直接获取值,找不到会到用- (id)valueForUndefinedKey:(NSString *)key,然后抛出异常。
直接查看最上方的图片逻辑更加清晰。

相关文章

  • KVC内部原理?KVC和KVO关系?

    KVC都不陌生,多多少少都用过,那么KVC内部原理是怎样的?KVC和KVO什么关系?使用KVC赋值会触发KVO吗?...

  • KVC,KVO

    KVC , KVO KVC和KVO的区别及应用 KVC/KVO原理 1. KVC键值编码 KVC,即是指NSKey...

  • GNUstep KVC/KVO探索(二):KVO的内部实现

    GNUstep KVC/KVO探索(一):KVC的内部实现GNUstep KVC/KVO探索(二):KVO的内部实...

  • GNUstep KVC/KVO探索(一):KVC的内部实现

    GNUstep KVC/KVO探索(一):KVC的内部实现GNUstep KVC/KVO探索(二):KVO的内部实...

  • ios基础——KVO、KVC

    KVO和KVC常见问题: 1.KVC和KVO是什么.2.KVC和KVO的原理是什么3.KVC和KVO的使用场景4....

  • KVO和KVC的使用及原理解析

    一 KVO基本使用 二 KVO本质原理讲解及代码验证 三 KVC基本使用 四 KVC设值原理 五 KVC取值原理 ...

  • iOS面试题(4) KVO KVC

    声明,不是原创,笔记均来自 群主大神~ 手动实现KVO 什么是KVO和KVC? KVO内部实现原理 NSNotif...

  • iOS-KVC和KVO

    重新整理一波KVO和KVC. 抛出几个常见问题 : 1.KVC和KVO是什么.2.KVC和KVO的原理是什么3.K...

  • KVC/KVO原理

    KVC/KVO原理 KVC setValue:forKey原理 调用setValue:forKey:方法,会直接寻...

  • 面试题

    1.简述KVC和KVO,其中KVO实现原理? KVC : 键值编码(Key-Value Coding),它是一种通...

网友评论

      本文标题:KVC内部原理?KVC和KVO关系?

      本文链接:https://www.haomeiwen.com/subject/cdftaltx.html