美文网首页
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 原理剖析

    1.KVO 前沿 KVO(Key-Value Observing, 键值观察), KVO的实现也依赖于runtim...

  • KVC

    KVC原理剖析 - CocoaChina_让移动开发更简单 iOS开发底层细究:KVC和KVO底层原理 | iOS...

  • iOS底层学习文章

    iOS黑魔法-Method Swizzling Objective-C 反射机制 KVC原理剖析 KVO原理分析及...

  • KVC,KVO

    KVC , KVO KVC和KVO的区别及应用 KVC/KVO原理 1. KVC键值编码 KVC,即是指NSKey...

  • KVO和KVC的使用及原理解析

    一 KVO基本使用 二 KVO本质原理讲解及代码验证 三 KVC基本使用 四 KVC设值原理 五 KVC取值原理 ...

  • 常见面试题--KVC和KVO

    1、KVO实现原理 2、KVC原理

  • ios基础——KVO、KVC

    KVO和KVC常见问题: 1.KVC和KVO是什么.2.KVC和KVO的原理是什么3.KVC和KVO的使用场景4....

  • KVC/KVO原理

    KVC/KVO原理 KVC setValue:forKey原理 调用setValue:forKey:方法,会直接寻...

  • KVC, KVO实现原理剖析

    iPhone程序开发 KVO/KVC实现机理分析是本文要介绍的内容,不多说,直接进入话题。我们来看详细内容。 Ob...

  • KVC原理剖析

    KVC原理剖析

网友评论

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

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