iOS KVC

2021-01-26  本文已影响0人  陈盼同学
//Dictionary赋值
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setValue:@"" forKey:@""];
[dic setObject:@"" forKey:@""];

字典里上面两句的区别(也仅限于字典)就是:
1, setObject:forkey:中value是不能够为nil的,不然会报错。
setValue: forKey:中value能够为nil,但是当value为nil的时候,会自动调用removeObject:forKey方法
2, setValue:forKey:中key的参数只能够是NSString类型,而setObject:forKey:的可以是任何类型

- (void)setValue:(id)value forKey:(NSString *)key;
- (void)removeObjectForKey:(id)aKey;
- (void)setObject:(id)anObject forKey:(id <NSCopying>)aKey;

[NSNumber numberWithInt:10]等价于@(10)也就是@10

KVC的全称是Key-Value Coding,俗称"键值编码",可以通过一个key来访问某个属性
常见的API有

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;

// 百思里面更换tabBar
[self setValue:[[KYTabBar alloc] init] forKeyPath:@"tabBar"];
首先一个.h里声明一个age只读属性的话@property (nonatomic , assign , readonly) int age;,.m如果不声明age为读写,那么是没法通过self.age = 1来赋值的,会报错提示该属性只读。但是.m里可以用_age = 1来赋值。那么会问,为什么外界不能通过_age来赋值呢,是因为_age私有在类里了。

//有个MJPerson类
#import <Foundation/Foundation.h>

@interface MJCat : NSObject
@property (assign, nonatomic) int weight;
@end

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

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

//在ViewController里实例化MJPerson
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;  //给age赋值
[person setValue:@20 forKey:@"age"]; //给age赋值
[person setValue:@30 forKeyPath:@"age"];  //给age赋值
NSLog(@"%@", [person valueForKey:@"age"]); //给age取值
NSLog(@"%@", [person valueForKeyPath:@"age"]);//给age取值

既然setValue: forKey和setValue: forKeyPath都可以给person赋值,那么setValue: forKeyPath有什么用呢,可以理解为forKeyPath是forKey的高级版,额外提供了根据“路径”赋值。
比如[person setValue:@10 forKeyPath:@"cat.weight"];//MJPerson里有个MJCat命名cat,MJCat里有个weight

论证 通过KVC修改属性会触发KVO吗?

写个MJPerson类
@interface MJPerson : NSObject
@property (nonatomic , assign) int age;
@end
控制器里
- (void)viewDidLoad {
    [super viewDidLoad];
    MJPerson *p1 = [[MJPerson alloc]init];
    p1.age = 3;
    [p1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
    [p1 setValue:@10 forKey:@"age"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {
    NSLog(@"---%@", change);
}
可以看到打印
2019-11-21 16:18:50.406089+0800 Test[7019:234396] ---{
    kind = 1;
    new = 10;
    old = 3;
}

先来验证[person setValue:@10 forKey:@"age"]原理

//首先新建一个person类 ,MJPerson.h里什么变量属性都屏蔽的那种
#import <Foundation/Foundation.h>

@interface MJPerson : NSObject
{
@public
    //    int age;
    //    int isAge;
    //    int _isAge;
    //    int _age;
}
//@property (assign, nonatomic) int age;
@end

//person类 ,MJPerson.m里先声明一些方法,暂时屏蔽,一步步证明
#import "MJPerson.h"

@implementation MJPerson

//- (int)getAge
//{
//    return 11;
//}

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

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

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

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

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

//- (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);
//}

// 默认的返回值就是YES
//+ (BOOL)accessInstanceVariablesDirectly
//{
//    return YES;
//}

@end

第一步

[p1 setValue:@10 forKey:@"age"];首先会按照
setKey:
_setKey:
顺序查找方法,找到后传递参数进行赋值,调用方法,没找到执行第二步
那么,如何论证第一步呢

先把这句放开
@property (assign, nonatomic) int age;
再把这句放开
- (void)setAge:(int)age
{
    NSLog(@"setAge: - %d", age);
}
打断点就能验证是通过setKey修改

然后上句setAge屏蔽了,然后属性也屏蔽了
在person类声明成员变量{  //为什么要成员变量呢,因为属性会默认生成set方法,没法验证_setAge
    @public
    int age;
}
再把这句放开,即可论证第一步了
- (void)_setAge:(int)age
{
    NSLog(@"_setAge: - %d", age);
}
所以,[p1 setValue:@10 forKey:@"age"]首先会找setKey方法,找不到会找_setKey方法,再找不到会执行第二步

第二步

查看accessInstanceVariablesDirectly方法的返回值
// 默认的返回值就是YES
+ (BOOL)accessInstanceVariablesDirectly
{
    return YES;
}
方法的返回值,如果是yes,执行第三步,如果是No,直接调用setValue:forUndefinedKey:并抛出异常NSUnknownKeyException

第三步

按照
_key
_isKey
key
isKey
顺序查找成员变量,找到直接赋值
//@interface MJPerson : NSObject
//{
//@public
//        int _age;
//        int _isAge;
//        int age;
//        int isAge;
//}
//
//@end
找不到直接调用setValue:forUndefinedKey:并抛出异常NSUnknownKeyException

可能会有个疑问,成员变量不是没有set吗,直接修改成员变量不会触发kvo,为啥kvo修改了成员变量后依旧会触发kvo呢?因为kvc里手动触发kvo,也就是willchangge和didchange。
在MJPerson类里写好下面两个方法,可以看到下面俩方法会被调用,所以就是kvo修改了成员变量后依旧会触发kvo,因为手动触发了。

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

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

验证[person valueForKey:@"age"]原理

第一步

按照
getKey
key
isKey
_key
顺序查找方法
也就是
- (int)getAge
{
    return 11;
}

- (int)age
{
    return 12;
}

- (int)isAge
{
    return 13;
}

- (int)_age
{
    return 14;
}
能找到就返回值,找不到执行第二步

第二步

查看
// 默认的返回值就是YES
+ (BOOL)accessInstanceVariablesDirectly
{
    return YES;
}
方法的返回值,yes就执行第三步,No就调用valueForUndefinedKey:
并抛出异常NSUnknownKeyException

第三步

按照
_key
_isKey
key
isKey
顺序查找成员变量,找到直接取值,找不到调用valueForUndefinedKey:
并抛出异常NSUnknownKeyException

论证[p1 setValue:@10 forKey:@"age"]和[person valueForKey:@"age"]的过程也就是KVC赋值和取值的过程

KVC的键值编码技术从可以强大的更改一个类私有成员的角度来说,是会破坏面向对象编程思想

上一篇下一篇

猜你喜欢

热点阅读