美文网首页
KVC & KVO 原理剖析

KVC & KVO 原理剖析

作者: d20cdad48d2e | 来源:发表于2017-07-19 19:05 被阅读51次

    1.KVO

    前沿

    KVO(Key-Value Observing, 键值观察), KVO的实现也依赖于runtime. 当你对一个对象进行观察时, 系统会动态创建一个类继承自原类, 然后重写被观察属性的setter方法. 然后重写的setter方法会负责在调用原setter方法前后通知观察者. KVO还会修改原对象的isa指针指向这个新类.

    我们知道, 对象是通过isa指针去查找自己是属于哪个类, 并去所在类的方法列表中查找方法的, 所以这个时候这个对象就自然地变成了新类的实例对象.

    不仅如此, Apple还重写了原类的- class方法, 试图欺骗我们, 这个类没有变, 还是原来的那个类(偷龙转凤). 只要我们懂得Runtime的原理, 这一切都只是掩耳盗铃罢了.

    KVO的缺陷

    Apple给我们提供的KVO不能通过block来回调处理, 只能通过下面这个方法来处理, 如果监听的属性多了, 或者监听了多个对象的属性, 那么这里就痛苦了, 要一直判断判断if else if else....多麻烦啊, 说实话我也不懂为什么Apple不提供多一个传block参数的方法

    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

    那么, 既然Apple没有帮我们实现, 那我们就手动实现一个呗, 先看下我们最终目标是什么样的 :

    小插曲:

    假设被观察者为A类的实例L,实现流程如下:
       1.在运行时,为A类创建一个子类B。 
       2.强行将实例L的类型改为B。
       3.为B类添加新的setter方法。 
       4.为B类添加观察者列表属性M。 
       5.将观察者的信息封装为类放入B类的M。 
    
    
      重点在第三项——kvo的setter方法如何写:
      因为是将实例L的类更改为了原类A的子类B,需要调用父类的对应的setter方法。
      由于在整个KVO过程中,观察的属性不一致则setter方法的名字也不一致。无法直接运用super调用,最简单的方法就是通过runtime来实现。
          1. 获得setter方法名 
          2. 根据setter方法名获得对应的setter消息 
          3. 根据setter方法名获得getter方法名 
          4. 根据getter方法名获得被观察属性当前值 
          5. 创建消息传递结构体(为了把setter消息转发给父类) 
          6. 把setter消息转发给父类 
          7. 遍历观察者列表,得到观察者信息,执行操作
    

    依据代码来分析原理:

    //
    //  NSObject+LeeKVO.m
    //  LeeSDWebImageLearn
    //
    //  Created by LiYang on 2017/7/19.
    //  Copyright © 2017年 LiYang. All rights reserved.
    //
    
    #import "NSObject+LeeKVO.h"
    #import <objc/runtime.h>
    #import <objc/message.h>
    
    //自添加观察者
    NSString *const kLEEKVOClassPrefix = @"LEEKVOClassPrefix_";
    //自添加观察者数组属性
    NSString *const kLEEKVOAssociatedObservers = @"LEEKVOAssociatedObservers";
    
    #pragma mark - LEEObservationInfo
    //观察者信息聚合类
    @interface LEEObservationInfo : NSObject
    
    @property (nonatomic, weak) NSObject *observer;
    @property (nonatomic, copy) NSString *key;
    @property (nonatomic, copy) LEEKVOCallBack block;
    
    @end
    
    @implementation LEEObservationInfo
    
    - (instancetype)initWithObserver:(NSObject *)observer Key:(NSString *)key block:(LEEKVOCallBack)block
    {
        self = [super init];
        if (self) {
            _observer  = observer;//观察者
            _key           = key;//观察者观察的属性
            _block       = block;//观察者察觉属性变化后执行的block
        }
        return self;
    }
    
    @end
    
    
    #pragma mark - Debug Help Methods
    static NSArray *ClassMethodNames(Class c){
        
        //根据类名,获取类的方法列表。
        NSMutableArray *array = [NSMutableArray array];
        unsigned int methodCount = 0;
        Method *methodList = class_copyMethodList(c, &methodCount);
        unsigned int i;
        for(i = 0; i < methodCount; i++) {
            [array addObject: NSStringFromSelector(method_getName(methodList[i]))];
        }
        free(methodList);
        return array;
        
    }
    
    static void PrintDescription(NSString *name, id obj){
        
        NSString *str = [NSString stringWithFormat:
                         @"%@: %@\n\tNSObject class %s\n\tRuntime class %s\n\timplements methods <%@>\n\n",
                         name,
                         obj,
                         class_getName([obj class]),
                         class_getName(object_getClass(obj)),
                         [ClassMethodNames(object_getClass(obj)) componentsJoinedByString:@", "]];
        printf("%s\n", [str UTF8String]);
        
    }
    
    
    #pragma mark - Helpers
    //根据setter方法名生成key
    static NSString * getterForSetter(NSString *setter){
        
        if (setter.length <=0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {
            return nil;
        }
        // remove 'set' at the begining and ':' at the end
        NSRange range = NSMakeRange(3, setter.length - 4);
        NSString *key = [setter substringWithRange:range];
        // lower case the first letter
        NSString *firstLetter = [[key substringToIndex:1] lowercaseString];
        key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1)
                                           withString:firstLetter];
        NSLog(@"key === %@",key);
        return key;
    }
    
    //根据getter方法名生成setter方法名
    static NSString * setterForGetter(NSString *getter){
        if (getter.length <= 0) {
            return nil;
        }
        // upper case the first letter
        NSString *firstLetter = [[getter substringToIndex:1] uppercaseString];
        NSString *remainingLetters = [getter substringFromIndex:1];
        // add 'set' at the begining and ':' at the end
        NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstLetter, remainingLetters];
        return setter;
    }
    
    
    #pragma mark - Overridden Methods
    static void kvo_setter(id self, SEL _cmd, id newValue){
        
        //根据SEL获得setter方法名
        NSString *setterName = NSStringFromSelector(_cmd);
        //进而获得getter方法名(就知道了属性的名字,即为被观察者的key)
        NSString *getterName = getterForSetter(setterName);
        if (!getterName) {
            NSString *reason = [NSString stringWithFormat:@"Object %@ does not have setter %@", self, setterName];
            @throw [NSException exceptionWithName:NSInvalidArgumentException
                                           reason:reason
                                         userInfo:nil];
            return;
        }
        //获取被观察者的属性的当前值
        id oldValue = [self valueForKey:getterName];
        //构建消息传递类,class为父类,实际接受者为自己
        struct objc_super superclazz = {
            .receiver = self,
            .super_class = class_getSuperclass(object_getClass(self))
        };
        //定义消息转发
        void (*objc_msgSendSuperSetValue)(void *, SEL, id) = (void *)objc_msgSendSuper;
        //利用消息传递类,转发消息——实质是调用父类的setter方法
        //更改被观察的属性的父类的值
        objc_msgSendSuperSetValue(&superclazz, _cmd, newValue);
        //获得观察者列表
        NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kLEEKVOAssociatedObservers));
        //轮训列表中的观察者哪些观察了这个属性,进而执行传入的block
        for (LEEObservationInfo *each in observers) {
            if ([each.key isEqualToString:getterName]) {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    each.block(self, getterName, oldValue, newValue);
                });
            }
        }
    }
    
    //欺骗用户,返回的是父类的class。。。笑点。。。
    static Class kvo_class(id self, SEL _cmd){
        Class clazz = object_getClass(self); // kvo_class
        Class superClazz = class_getSuperclass(clazz); // origin_class
        return superClazz;
    }
    
    
    #pragma mark - KVO Category
    @implementation NSObject (LeeKVO)
    
    -(void)Lee_addObserver:(NSObject *)observer
                    forKey:(NSString *)key
                andHandler:(LEEKVOCallBack)handlerBack{
    
        //获得setter方法名,然后生成SEL
        SEL setterSelector = NSSelectorFromString(setterForGetter(key));
        //在类方法列表中寻找setter方法
        Method setterMethod = class_getInstanceMethod([self class], setterSelector);
        //如果没有,则抛出异常
        if (!setterMethod) {
            NSString *reason = [NSString stringWithFormat:@"Object %@ does not have a setter for key %@", self, key];
            @throw [NSException exceptionWithName:NSInvalidArgumentException
                                           reason:reason
                                         userInfo:nil];
            
            return;
        }
        //得到当前类名
        Class clazz                  = object_getClass(self);
        NSString *clazzName = NSStringFromClass(clazz);
        //创建KVO子类
        if (![clazzName hasPrefix:kLEEKVOClassPrefix]) {
            clazz = [self makeKvoClassWithOriginalClassName:clazzName];
            //强行更改自身类型,该函数的作用是为一个  对象  设置一个指定的带有前缀的类
            object_setClass(self, clazz);
        }
        //如果不存在setter方法则添加setter方法
        if (![self hasSelector:setterSelector]) {
            const char *types = method_getTypeEncoding(setterMethod);
            class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
        }
        //存放观察者信息的类
        LEEObservationInfo *info = [[LEEObservationInfo alloc] initWithObserver:observer Key:key block:handlerBack];
        //获得观察者信息列表
        NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kLEEKVOAssociatedObservers));
        //如果不存在则添加观察者列表属性
        if (!observers) {
            observers = [NSMutableArray array];
            objc_setAssociatedObject(self, (__bridge const void *)(kLEEKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        //将观察者信息放入观察者列表
        [observers addObject:info];
        
    }
    //移除观察者
    -(void)Lee_removeObserver:(NSObject *)observer forKey:(NSString *)key {
    
        NSMutableArray* observers = objc_getAssociatedObject(self, (__bridge const void *)(kLEEKVOAssociatedObservers));
        LEEObservationInfo *infoToRemove;
        for (LEEObservationInfo* info in observers) {
            if (info.observer == observer && [info.key isEqual:key]) {
                infoToRemove = info;
                break;
            }
        }
        [observers removeObject:infoToRemove];
        
    }
    
    //新建子类
    - (Class)makeKvoClassWithOriginalClassName:(NSString *)sourceClass{
        //为中间类加上自定义前缀,方便自己识别
        NSString *kvoClassName = [kLEEKVOClassPrefix stringByAppendingString:sourceClass];
        //生成类
        Class class = NSClassFromString(kvoClassName);
        if (class) {
            return class;
        }
        // 貌似在这里已经被子类化了。。操。
        Class originalClass = object_getClass(self);
        //新建类,KVO类,父类是originalClass  添加类 superclass 类是父类   name 类的名字  size_t 类占的空间
        Class kvoClass       = objc_allocateClassPair(originalClass, kvoClassName.UTF8String, 0);
        //为KVO类替换掉class方法 —— 添加了之后之前的方法应该是和类目一样被放在后面去了 无法调用??
        Method clazzMethod = class_getInstanceMethod(originalClass, @selector(class));
        const char *types       = method_getTypeEncoding(clazzMethod);
        class_addMethod(kvoClass, @selector(class), (IMP)kvo_class, types);
        //注册KVO类
        objc_registerClassPair(kvoClass);
        //返回KVO类
        return kvoClass;
    }
    
    //检查类是否包含这个方法
    - (BOOL)hasSelector:(SEL)selector{
        Class clazz = object_getClass(self);
        unsigned int methodCount = 0;
        Method* methodList = class_copyMethodList(clazz, &methodCount);
        for (unsigned int i = 0; i < methodCount; i++) {
            SEL thisSelector = method_getName(methodList[i]);
            if (thisSelector == selector) {
                free(methodList);
                return YES;
            }
        }
        free(methodList);
        return NO;
    }
    
    
    
    @end
    
    

    设计思路是:

    • 给一个类的对象属性添加观察者时候,首先判断这个属性有没有setter方法如果没有抛出异常
    • 给当前类的对象创建一个继承自当前对象的类的子类,然后把当前操作的对象指定到创建的这个子类
      • 此处添加了一个class方法到子类中,相当于调用时候,给用户造成的假象是仍然是原来的类
    • 给创建的子类添加对应于要观察的属性的setter方法,并把要观察的属性的相关信息添加到信息列表中
      • 此处添加的setter方法是在子类中重写后的setter方法
    • 当外界触发了settter方法的时候,就会进入这个子类的setter方法中来,同时通过kvc获取前一次的值
    • 构建一个给父类发消息的结构体对象,然后将消息发送出去,其中包括了发送的目的类,要执行的方法,以及参数值。
    • 接着就是对外回调出结果

    2.KVC

    1.KVC 是 Key-Value-Coding 的简称。

    2.KVC 是一种可以直接通过字符串的名字 key 来访问类属性的机制,而不是通过调用 setter、getter 方法去访问。

    3.我们可以通过在运行时动态的访问和修改对象的属性。而不是在编译时确定,KVC 是 iOS 开发中的黑魔法之一。

    KVC 的主要方法:
    • 设置值:
    // value的值为OC对象,如果是基本数据类型要包装成NSNumber或NSValue
    - (void)setValue:(id)value forKey:(NSString *)key;
    
    // keyPath键路径,类型为xx.xx
    - (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
    
    // 它的默认实现是抛出异常,可以重写这个函数做错误处理。
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key;
    
    
    • 获取值:
    - (id)valueForKey:(NSString *)key;
    
    - (id)valueForKeyPath:(NSString *)keyPath;
    
    // 如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常
    - (id)valueForUndefinedKey:(NSString *)key;
    
    

    NSKeyValueCoding 类别中还有其他的一些方法:

    // 允许直接访问实例变量,默认返回YES。如果某个类重写了这个方法,且返回NO,则KVC不可以访问该类。
    + (BOOL)accessInstanceVariablesDirectly;
    
    // 这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回
    - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
    
    // 如果你在setValue方法时面给Value传nil,则会调用这个方法
    - (void)setNilValueForKey:(NSString *)key;
    
    // 输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
    - (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
    
    // KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
    - (BOOL)validateValue:(id)ioValue forKey:(NSString *)inKey error:(NSError)outError;
    
    

    小试牛刀:

    //
    //  People.h
    //  LeeSDWebImageLearn
    //
    //  Created by LiYang on 2017/7/19.
    //  Copyright © 2017年 LiYang. All rights reserved.
    //
    
    #import <Foundation/Foundation.h>
    
    @interface People : NSObject{
    
    @private
       NSString * _girlFriend;
       
    }
    @property(nonatomic,copy,readonly)NSString * name;
    
    @end
    
    

    这里给只读河私有的属性和变量赋值操作

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
        [self.peo setValue:@"beautifulgirl" forKey:@"_girlFriend"];
        [self.peo setValue:@"oliverlee" forKey:@"name"];
     
        NSLog(@"%@,%@",self.peo.name,[self.peo valueForKey:@"_girlFriend"]);
        
    }
    
    KVC 实现细节

    -(void)setValue:(id)value forKey:(NSString *)key;

    • 首先搜索setter 方法,有就赋值
    • 如果没有setter方法,再检查类方法+(BOOL)accessInstanceVariablesDirectly
      • 返回YES 继续查找相相关变量 _key ,_isKey,key,isKey 顺序检索,未找到调用
        setValue:forUndefinedKey: 如果实现了这个方法不会跑出异常,为实现会奔溃
      • 返回NO 执行 setValue:forUndefinedKey: 如果实现了这个方法不会跑出异常,为实现会奔溃
    • 未找到 的话 调用setValue:forUndefinedKey: 如果实现了这个方法不会跑出异常,为实现会奔溃

    -(id)valueForKey:(NSString *)key;

    • 首先查找getter 方法,找到直接调用,如果是 bool、int、float 等基本数据类型,会做 NSNumber 的转换。
    • 如果没查到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly
      • 返回 NO,则执行valueForUNdefinedKey:若未实现,则奔溃
      • 返回 YES,按 _key,_isKey,key,isKey的顺序检索成员名,
    • 还没有找到的话,调用valueForUndefinedKey:若未实现,则奔溃
    KVC 与点语法比较

    用 KVC 访问属性和用点语法访问属性的区别:

    • 用点语法编译器会做预编译检查,访问不存在的属性编译器会报错,但是用 KVC 方式编译器无法做检查,如果有错误只能运行的时候才能发现(crash)。
    • 相比点语法用 KVC 方式 KVC 的效率会稍低一点,但是灵活,可以在程序运行时决定访问哪些属性。
    • 用 KVC 可以访问对象的私有成员变量。
    KVC 运用
    • 字典转模型
    -(void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
    
    #import <Foundation/Foundation.h>
    @interface timeModel :NSObject
    @property(nonatomic,copy)NSString * timer;
    @property(nonatomic,copy)NSString * name;
    @end
    @interface People : NSObject{
    @private
        NSString * _girlFriend; 
    }
    @property(nonatomic,copy,readonly)NSString * name;
    @property(nonatomic,assign)int age;
    @property(nonatomic,strong)id arr;
    
    //  People.m
    //  LeeSDWebImageLearn
    //  Created by LiYang on 2017/7/19.
    //  Copyright © 2017年 LiYang. All rights reserved.
    //
    #import "People.h"
    @implementation timeModel
    -(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    }
    @end
    @implementation People
    -(void)setValue:(id)value forKey:(NSString *)key{
        if ([key isEqualToString:@"arr"]) {
            NSMutableArray *array = (id)value;
            NSMutableArray * modelArr = [NSMutableArray array];
            for (int i =0; i < array.count; i++) {
                NSDictionary * dic = array[i];
                timeModel * model = [[timeModel alloc] init];
                [model setValuesForKeysWithDictionary:dic];
                [modelArr addObject:model];
            }
            value = modelArr;
        }
        [super setValue:value forKey:key];
    }
    -(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    }
    @end
    
        [self.peo setValue:@"beautifulgirl" forKey:@"_girlFriend"];
        [self.peo setValue:@"oliverlee" forKey:@"name"];
        NSLog(@"%@,%@",self.peo.name,[self.peo valueForKey:@"_girlFriend"]);
        NSDictionary * peoDic = @{@"name":@"liyang",@"age":[NSNumber numberWithInt:100],@"key":@"hello",@"arr":@[@{@"name":@"xxxx",@"timer":@"yyyyy"},@{@"name":@"xxxx",@"timer":@"yyyyy"},@{@"name":@"xxxx",@"timer":@"yyyyy"},@{@"name":@"xxxx",@"timer":@"yyyyy"}]};
        [self.peo setValuesForKeysWithDictionary:peoDic];
        NSLog(@"%@,%d,%@",self.peo.name,self.peo.age,self.peo.arr);
    
    2017-07-19 18:07:29.759 LeeSDWebImageLearn[3835:437377] oliverlee,beautifulgirl
    2017-07-19 18:07:29.760 LeeSDWebImageLearn[3835:437377] liyang,100,(
        "<timeModel: 0x608000022860>",
        "<timeModel: 0x608000022740>",
        "<timeModel: 0x6080000225e0>",
        "<timeModel: 0x6080002245e0>"
    )
    

    上面是用KVC 转换的嵌套类型,用着很活,自己去定义重写她的几个方法就好了,比较轻量级别

    • 修改私有属性

    1.修改 TextField 的 placeholder:

    [_textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];   
    
    [_textField setValue:[UIFont systemFontOfSize:14] forKeyPath:@“_placeholderLabel.font"];
    

    2.修改 UIPageControl 的图片:

    [_pageControl setValue:[UIImage imageNamed:@"selected"] forKeyPath:@"_currentPageImage"];
    
    [_pageControl setValue:[UIImage imageNamed:@"unselected"] forKeyPath:@"_pageImage"];
    
    
    • 集合类操作符(用不多)

    获取集合类的 count,max,min,avg,svm。隐藏函数,访问时候要加@,确保操作的属性为数字类型,否则会报错。

    • @count 返回一个值为集合中对象总数的NSNumber对象;
    • @avg 首先把集合中的每个对象都转换为double类型,然后计算其平均值,并返回这个平均值的NSNumber对象;
    • @max 使用compare:方法来确定最大值,并返回最大值的NSNumber对象.所以为了保证其正常比较,集合中所有的对象都必须支持和另一个对象的比较,保证其可比性;
    • @min 原理和@max一样,其返回的是集合中的最小值的NSNumber对象;
    • @sum 首先把集合中的每个对象都转换为double类型,然后计算其总和,并返回总和的NSNumber对象;
    • Notice: 所有的集合操作,除了@count外,其他都需要有右边的keyPath(一般为属性名),@count右边的keyPath可写可不写.

    1.Notice:若操作对象(数组/集合)内的元素本身就是 NSNumber 对象,那么可以这样写.

    
        NSMutableArray * numArray = @[].mutableCopy;
        for (int i =0 ; i< 15; i++) {
            [numArray addObject:@(i)];
        }
        NSLog(@"%@",[numArray valueForKeyPath:@"@count"]);
        NSLog(@"%@",[numArray valueForKeyPath:@"@min.self"]);
        NSLog(@"%@",[numArray valueForKeyPath:@"@max.self"]);
        NSLog(@"%@",[numArray valueForKeyPath:@"@sum.self"]);
        NSLog(@"%@",[numArray valueForKeyPath:@"@avg.self"]);
    
    

    控制台:

    2017-07-19 18:56:37.071 LeeSDWebImageLearn[4247:488147] 15
    2017-07-19 18:56:37.072 LeeSDWebImageLearn[4247:488147] 0
    2017-07-19 18:56:37.072 LeeSDWebImageLearn[4247:488147] 14
    2017-07-19 18:56:37.072 LeeSDWebImageLearn[4247:488147] 105
    2017-07-19 18:56:37.073 LeeSDWebImageLearn[4247:488147] 7
    

    2.若数组中的元素为模型类对象可以这样写

        NSMutableArray * numArray = @[].mutableCopy;
        //模拟数据
        ProductModel *productA = [[ProductModel alloc] init];
        productA.price = 99.0;
        productA.name = @"iPod";
        
        ProductModel *productB = [[ProductModel alloc] init];
        productB.price = 199.0;
        productB.name = @"iMac";
        
        ProductModel *productC = [[ProductModel alloc] init];
        productC.price = 299.0;
        productC.name = @"iPhone";
        
        ProductModel *productD = [[ProductModel alloc] init];
        productD.price = 199.0;
        productD.name = @"iPhone";
        
        [numArray addObjectsFromArray:@[productA,productB,productC,productD]];
        NSLog(@"%@",[numArray valueForKeyPath:@"@count"]);
        NSLog(@"%@",[numArray valueForKeyPath:@"@min.price"]);
        NSLog(@"%@",[numArray valueForKeyPath:@"@max.price"]);
        NSLog(@"%@",[numArray valueForKeyPath:@"@sum.price"]);
        NSLog(@"%@",[numArray valueForKeyPath:@"@avg.price"]);
    
    KVC 总结

    键值编码是一种间接访问对象的属性使用字符串来标识属性,而不是通过调用存取方法直接或通过实例变量访问的机制,非对象类型的变量将被自动封装或者解封成对象,很多情况下会简化程序代码。

    优点:

    • 不需要通过 setter、getter 方法去访问对象的属性,可以访问对象的私有属性。
    • 可以轻松处理集合类(NSArray)。

    缺点:

    • 一旦使用 KVC 你的编译器无法检查出错误,即不会对设置的键、键值路径进行错误检查。
    • 执行效率要低于 setter 和 getter 方法。因为使用 KVC 键值编码,它必须先解析字符串,然后在设置或者访问对象的实例变量。
    • 使用 KVC 会破坏类的封装性。

    友情链接:
    http://www.jianshu.com/p/d702286b0b49
    https://opensource.apple.com/
    http://www.jianshu.com/p/4748ef75126a
    http://blog.csdn.net/u010123208/article/details/4042514
    http://www.jianshu.com/p/2c2af5695904

    相关文章

      网友评论

          本文标题:KVC & KVO 原理剖析

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