KVC底层实现原理

2019-03-03  本文已影响0人  DPL1024

KVC基本使用

定义DPLPerson和DPLCat类,DPLCat类拥有weight属性,DPLPerson类拥有agecat属性。

// 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设值原理

KVC设值流程图.png

代码验证:

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_isKeykeyisKey

    @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取值原理

KVC取值原理.png

代码验证

step1

删除所有成员变量和属性,实现方法getAge


- (int)getAge {
    NSLog(@"getAge");
    
    return 100;
}
    

执行程序,打印

getAge
person.age --- 100

按照异常步骤,依次验证keyisKey_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

按以上步骤,依次验证ageisAge

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;
}

可以看到,确实调用了这两个方法。

上一篇下一篇

猜你喜欢

热点阅读