iOS KVC

作者: 陈盼同学 | 来源:发表于2021-01-26 15:03 被阅读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的键值编码技术从可以强大的更改一个类私有成员的角度来说,是会破坏面向对象编程思想

    相关文章

      网友评论

          本文标题:iOS KVC

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