美文网首页
KVC底层原理

KVC底层原理

作者: 曼谷第一开膛手 | 来源:发表于2018-06-14 14:32 被阅读56次

    接触iOS也有三四年了,感受颇多,经历过高潮也经过低潮,大浪淘沙 靠的都是硬功夫,经历过迷茫期,怀疑过自己,但是还是得坚持,生活就是这样,什么干好都是不容易的,最近有空来对这几年经历做些总结,每个人的经历都不一样,仁者见仁智者见智哈。
    在一家公司待久了 慢慢都会变成业务性程序员,毕竟小企业还是以公司发展为目的,所以慢慢的对一些技术要求不是那么重要了,以能实现功能目的。所以我们大部分都是搬砖程序员,现在对iOS学习路线有些见解

    1.原理,底层。知其然知其所以然,研究底层的过程中会恍然大悟,这是进阶必须要经历的阶段
    2. 编程思想,设计模式,架构设计。项目中实战
    3. 专业领域 --市场(ARKit\CoreML Open GL 等框架)---> 跨平台

    扯得有点远了,言归正传。先上一张图看下API


    270478-8035b5dfae171ec4.png.jpeg

    KVC 全称 Key Value Coding
    KVC主要对三种类型进行操作,基础数据类型及常量,对象类型,集合类型。

    @interface BankAccount : NSObject
    @property (nonatomic, strong) NSNumber *currentBalance;
    @property (nonatomic, strong) Person *owner;
    @property (nonatomic, strong) NSArray<transaction *> *transactions;
    @end</transaction *>
    

    在使用KVC时,直接将属性名当做key,并设置value,即可对属性进行赋值。

    [myAccount setValue:@(100.0) forKey:@"currentBalance"];
    

    除了对当前对象的属性进行赋值外,还可以对其更“深层”的对象进行赋值。例如对当前对象的address属性的street属性进行赋值。KVC进行多级访问时,直接类似于属性调用一样用点语法进行访问即可

    [myAccount setValue:@"中关村大街" forKeyPath:@"address.street"];
    
    

    通过keyPath对数组进行取值时,并且数组中存储的对象类型都相同,可以通过valueForKeyPath:方法指定取出数组中所有对象的某个字段。例如下面例子中,通过valueForKeyPath:将数组中所有对象的name属性值取出,并放入一个数组中返回。

    NSArray *names = [array valueForKeyPath:@"name"];
    

    通过keyPath对数组进行取值时,并且数组中存储的对象类型都相同,可以通过valueForKeyPath:方法指定取出数组中所有对象的某个字段。例如下面例子中,通过valueForKeyPath:将数组中所有对象的name属性值取出,并放入一个数组中返回

    NSArray *names = [array valueForKeyPath:@"name"];
    
    多值操作

    KVC还有更强大的功能,可以根据给定的一组key,获取到一组value,并且以字典的形式返回,获取到字典后可以通过key从字典中获取到value。

    - (NSDictionary<nsstring *, id> *)dictionaryWithValuesForKeys:(NSArray<nsstring *> *)keys;</nsstring *></nsstring *, id>
    
    NSArray *keys = @[@"name",@"age"];
    NSDictionary *dict = [person dictionaryWithValuesForKeys:keys];
    {
        age = 222;
        name = afei;
    }
    
    

    同样,也可以通过KVC进行批量赋值。在对象调用setValuesForKeysWithDictionary:方法时,可以传入一个包含key、value的字典进去,KVC可以将所有数据按照属性名和字典的key进行匹配,并将value给User对象的属性赋值。
    赋值时会遇到一些问题,例如服务器会返回一个id字段,但是对于客户端来说id是系统保留字段,可以重写setValue:forUndefinedKey:方法并在内部处理id参数的赋值。

    - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
        if ([key isEqualToString:@"id"]) {
            self.userId = [value integerValue];
        }
    }
    

    关于KVC valueForKey:key 的调用顺序
    先调用相关方法,先后顺序:

    getter 方法:getKey(注意Key首字母大写) --> key -->isKey
    如果没有相关方法:看 +(BOOl)accessInstanceVariablesDirectly 返回值
    默认 YES 找成员变量,先后顺序:_key ->_isKey ->key -> isKey
    NO 异常 valueForUnderFineKey 可以重写补救

    异常信息

    当根据KVC搜索规则,没有搜索到对应的key或者keyPath,则会调用对应的异常方法。异常方法的默认实现,在异常发生时会抛出一个NSUndefinedKeyException的异常,并且应用程序Crash。
    我们可以重写下面两个方法,根据业务需求合理的处理KVC导致的异常。

    - (nullable id)valueForUndefinedKey:(NSString *)key;
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    
    
    异常处理

    当通过KVC给某个非对象的属性赋值为nil时,此时KVC会调用属性所属对象的setNilValueForKey:方法,并抛出NSInvalidArgumentException的异常,并使应用程序Crash。

    我们可以通过重写下面方法,在发生这种异常时进行处理。例如给name赋值为nil的时候,就可以重写setNilValueForKey:方法并表示name是空的。

    - (void)setNilValueForKey:(NSString *)key {
        if ([key isEqualToString:@"name"]) {
            [self setValue:@"" forKey:@”name”];
        } else {
            [super setNilValueForKey:key];
        }
    }
    
    集合属性操作

    根据KVO的实现原理,是在运行时生成新的子类并重写其setter方法,在其内容发生改变时发送消息。但这只是对属性直接进行赋值会触发,如果属性是容器对象,对容器对象进行add或remove操作,则不会调用KVO的方法。可以通过KVC对应的API来配合使用,使容器对象内部发生改变时也能触发KVO。

    在进行容器对象操作时,先调用下面方法通过key或者keyPath获取集合对象,然后再对容器对象进行add或remove等操作时,就会触发KVO的消息通知了。

    - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
    - (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
    - (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
    
    集合运算符

    @count @max @min @sum @avg

    私有访问

    根据上面的实现原理我们知道,KVC本质上是操作方法列表以及在内存中查找实例变量。我们可以利用这个特性访问类的私有变量,例如下面在.m中定义的私有成员变量和属性,都可以通过KVC的方式访问。

    这个操作对readonly的属性,@protected的成员变量,都可以正常访问。如果不想让外界访问类的成员变量,则可以将accessInstanceVariablesDirectly属性赋值为NO。
    KVC在实践中也有很多用处,例如UITabbar或UIPageControl这样的控件,系统已经为我们封装好了,但是对于一些样式的改变并没有提供足够的API,这种情况就需要我们用KVC进行操作了。
    可以自定义一个UITabbar对象,然后在内部创建自己想要的视图,并通过layoutSubviews方法在内部进行重新布局。然后通过KVC的方式,将UITabbarController的tabbar属性替换为自定义的类即可。

    安全性检查

    KVC存在一个问题在于,因为传入的key或keyPath是一个字符串,这样很容易写错或者属性自身修改后字符串忘记修改,这样会导致Crash。

    可以利用iOS的反射机制来规避这个问题,通过@selector()获取到方法的SEL,然后通过NSStringFromSelector()将SEL反射为字符串。这样在@selector()中传入方法名的过程中,编译器会有合法性检查,如果方法不存在或未实现会报黄色警告。

    [self valueForKey:NSStringFromSelector(@selector(object))];
    
    

    相关文章

      网友评论

          本文标题:KVC底层原理

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