KVC分析

作者: 浅墨入画 | 来源:发表于2021-09-03 19:56 被阅读0次

methodswizzling面试题

method-swizzling的含义是方法交换,其主要作用是在运行时将一个方法的实现替换成另一个方法的实现。iOS中每个类都维护着一个方法列表methodList,methodList中有不同的方法即Method,每个方法中包含了方法的sel和IMP,方法交换就是将sel和imp原本的对应断开,并将sel和新的IMP生成对应关系。

交换前后的sel和IMP的对应关系如下图

方法交换原理
method-swizzling使用过程中的一些坑点
坑点一 method-swizzling使用过程中的一次性问题

一次性是指:mehod-swizzling写在load方法中,而load方法会主动调用多次,这样会导致方法的重复交换,使方法sel的指向又恢复成原来的imp

解决方案
可以通过单例设计原则,使方法交换只执行一次,在OC中可以通过dispatch_once实现单例

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [LGRuntimeTool lg_betterMethodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)];
    });
}
坑点二 子类没有实现,父类实现了

调试一
创建LGPersonLGStudent类,其中LGPerson中实现了personInstanceMethod,而LGStudent继承自LGPerson,没有实现personInstanceMethod,运行代码会出现什么问题?

//*********LGPerson类*********
@interface LGPerson : NSObject
- (void)personInstanceMethod;
@end

@implementation LGPerson
- (void)personInstanceMethod{
    NSLog(@"person对象方法:%s",__func__);  
}
@end

//*********LGStudent类*********
@interface LGStudent : LGPerson
@end

@implementation LGStudent

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)];
    });
}

- (void)lg_studentInstanceMethod{
    [self lg_studentInstanceMethod]; //lg_studentInstanceMethod -/-> personInstanceMethod
    NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__);
}

@end

//封装好的method-swizzling方法
@implementation LGRuntimeTool
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"传入的交换类不能为空");

    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    method_exchangeImplementations(oriMethod, swiMethod);
}

//*********调用*********
- (void)viewDidLoad {
    [super viewDidLoad];

    LGStudent *s = [[LGStudent alloc] init];
    [s personInstanceMethod];
    
    LGPerson *p = [[LGPerson alloc] init];
    [p personInstanceMethod];
}
image.png
  • [s personInstanceMethod];中不报错是因为student中的imp交换成了lg_studentInstanceMethod,而LGStudent中有这个方法,所以不会报错
  • 崩溃的点在于[p personInstanceMethod];,其本质原因:LGStudent中进行了方法交换,将person中imp交换成了 LGStudent中的lg_studentInstanceMethod,然后需要去LGPerson中的找lg_studentInstanceMethod,但是LGPerson中没有lg_studentInstanceMethod方法,即相关的imp找不到就崩溃了

优化:通过class_addMethod尝试添加你要交换的方法,避免imp找不到

  • 如果添加成功即类中没有这个方法,则通过class_replaceMethod进行替换,其内部会调用class_addMethod进行添加
  • 如果添加不成功即类中有这个方法,则通过method_exchangeImplementations进行交换
+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"传入的交换类不能为空");
    
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
   
    // 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
    // 交换自己没有实现的方法:
    //   首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
    //   然后再将父类的IMP给swizzle  personInstanceMethod(imp) -> swizzledSEL 

    BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));

    if (success) {// 自己没有 - 交换 - 没有父类进行处理 (重写一个)
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{ // 自己有
        method_exchangeImplementations(oriMethod, swiMethod);
    }   
}

调试二
删除LGPerson中的personInstanceMethod方法实现

//*********LGPerson类*********
@interface LGPerson : NSObject
- (void)personInstanceMethod;
@end

@implementation LGPerson
@end

//*********LGStudent类*********
@implementation LGStudent

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 使用上面优化过的方法
        [LGRuntimeTool lg_betterMethodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)];
    });
}
image.png

原因是[self lg_studentInstanceMethod];出现了死递归,其中交换方法method_exchangeImplementations(oriMethod, swiMethod);中的oriMethod为nil,交换失败
再次优化:

+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"传入的交换类不能为空");
    
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    
    if (!oriMethod) { // 避免动作没有意义
        // 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
            NSLog(@"来了一个空的 imp");
        }));
    }
    
    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }
}

KVC简介

KVC全称Key-Value Coding,也称键值编码。是由NSKeyValueCoding非正式协议启用的一种机制,对象采用该协议来间接访问其属性。既可以通过字符串key访问某个属性,也可以通过字符串路径的形式访问。

相关API
  • 常用API
//直接通过Key来取值
- (nullable id)valueForKey:(NSString *)key;
//通过Key来设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;

//通过KeyPath来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath; 
//通过KeyPath来设值                 
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  
  • 其他方法
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly;

//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

//如果Key不存在,且KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;

//和上一个方法一样,但这个方法是设值。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;

//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

苹果官方文档解释KVC

苹果官方文档学习

image.png

Documentation Archive

image.png

KVC设值和取值过程

设值过程

我们通过Key-Value Coding Programming Guide苹果官方文档来探索setValue:forKey的原理

//*********LGPerson类*********
//MARK: - setKey. 的流程分析
- (void)setName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}

- (void)_setName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}

//*********调用*********
- (void)viewDidLoad {
    [super viewDidLoad];
    LGPerson *person = [[LGPerson alloc] init];
    // 1: KVC - 设置值的过程 setValue 分析调用过程
     [person setValue:@"LG_Cooci" forKey:@"name"];
}

// 控制台只调用了setName方法,并不会调用_setName方法
2021-09-02 23:17:20.423229+0800 002-KVC取值&赋值过程[3549:12869048] -[LGPerson setName:] - LG_Cooci
  • 首先查找是否有这三种setter方法,按照查找顺序为set<Key>:-> _set<Key> -> setIs<Key>
    但凡找到其中任意一个setter方法,便直接设置属性的value
    如果没有,进入下一步
//*********LGPerson.h*********
@interface LGPerson : NSObject{
    @public
    NSString *_isName;
    NSString *name;
    NSString *isName;
    NSString *_name;
}

//*********LGPerson.m*********
#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
    return YES;
}

//*********调用*********
- (void)viewDidLoad {
    [super viewDidLoad];
    LGPerson *person = [[LGPerson alloc] init];
    // 1: KVC - 设置值的过程 setValue 分析调用过程
    [person setValue:@"LG_Cooci" forKey:@"name"];
    NSLog(@"取值:%@",[person valueForKey:@"name"]); 
}
2021-09-02 23:21:49.262497+0800 002-KVC取值&赋值过程[3575:12872987] 取值:LG_Cooci

//************************************************************
//*********LGPerson.h*********
@interface LGPerson : NSObject{
    @public
    NSString *_isName;
    // NSString *name;
    NSString *isName;
    NSString *_name;
}

//*********调用*********
- (void)viewDidLoad {
    [super viewDidLoad];
    LGPerson *person = [[LGPerson alloc] init];
    // 1: KVC - 设置值的过程 setValue 分析调用过程
    [person setValue:@"LG_Cooci" forKey:@"name"];
    NSLog(@"取值:%@",[person valueForKey:@"_name"]); 
}
// _name同样能取到值
2021-09-02 23:26:41.563610+0800 002-KVC取值&赋值过程[3627:12878756] 取值:LG_Cooci
  • 接下来查找accessInstanceVariablesDirectly是否返回YES,返回NO直接进入下一步
    如果返回YES,则间接访问实例变量进行赋值,顺序为_<key> -> _is<Key> -> <key> -> is<Key>
    如果都没有进入下一步
  • 系统会执行setValue:forUndefinedKey:方法,默认抛出NSUndefinedKeyException类型的异常
image.png
取值过程
//*********LGPerson.m*********
//MARK: - valueForKey 流程分析 - get<Key>, <key>, is<Key>, or _<key>,
- (NSString *)getName{
    return NSStringFromSelector(_cmd);
}

- (NSString *)name{
    return NSStringFromSelector(_cmd);
}

- (NSString *)isName{
    return NSStringFromSelector(_cmd);
}

- (NSString *)_name{
    return NSStringFromSelector(_cmd);
}

//*********调用*********
- (void)viewDidLoad {
    [super viewDidLoad];
    LGPerson *person = [[LGPerson alloc] init];
    // 1: KVC - 设置值的过程 setValue 分析调用过程
     [person setValue:@"LG_Cooci" forKey:@"name"];
    // 2: KVC - 取值的过程
    // _name
    person->_name = @"_name";
    person->_isName = @"_isName";
    person->name = @"name";
    person->isName = @"isName";
    NSLog(@"取值:%@",[person valueForKey:@"name"]);
}
// 调用的是getName方法
2021-09-02 23:42:50.114460+0800 002-KVC取值&赋值过程[3717:12891729] 取值:getName

我们通过Key-Value Coding Programming Guide苹果官方文档来探索setValue:forKey的原理

同样的通过官方文档探索valueForKey的原理

  • 1.首先查找getter方法,按照get<Key> -> <key> -> is<Key> -> _<key>的方法顺序查找
    如果找到,则进入第五步
    如果没有找到,则进入第二步
  • 2.如果第一步中的getter方法没有找到,KVC会查找countOf <Key>objectIn <Key> AtIndex :<key> AtIndexes :
    如果找到countOf <Key>和其他两个中的一个,则会创建一个响应所有NSArray方法的集合代理对象,并返回该对象,即NSKeyValueArray,是NSArray的子类。代理对象随后将接收到的所有NSArray消息转换为countOf<Key>objectIn<Key> AtIndex:<key>AtIndexes:消息的某种组合,用来创建键值编码对象。如果原始对象还实现了一个名为get<Key>:range:之类的可选方法,则代理对象也将在适当时使用该方法(注意:方法名的命名规则要符合KVC的标准命名方法,包括方法签名。)
    如果没有找到这三个访问数组的,请继续进入第三步
  • 3.如果没有找到上面的几种方法,则会同时查找countOf <Key>enumeratorOf<Key>memberOf<Key>这三个方法
    如果这三个方法都找到,则会创建一个响应所有NSSet方法的集合代理对象,并返回该对象,此代理对象随后将其收到的所有NSSet消息转换为countOf<Key>enumeratorOf<Key>memberOf<Key>:消息的某种组合,用于创建它的对象
    如果还是没有找到,则进入第四步
  • 4.如果还没有找到,检查类方法InstanceVariablesDirectly是否YES,依次搜索_<key>,_is<Key><key>is<Key>的实例变量
    如果搜到,直接获取实例变量的值,进入第五步
  • 5.根据搜索到的属性值的类型,返回不同的结果
    如果是对象指针,则直接返回结果
    如果是NSNumber支持的标量类型,则将其存储在NSNumber实例中并返回它
    如果是NSNumber不支持的标量类型,请转换为NSValue对象并返回该对象
  • 6.如果上面5步的方法均失败,系统会执行该对象的valueForUndefinedKey:方法,默认抛出NSUndefinedKeyException类型的异常

KVC通过valueForKey:方法取值的流程,以设置LGPerson对象person的属性name为例,如下图所示

image.png

KVC自定义实现

通过给NSObject添加分类LGKVC,实现自定义的lg_setValue: forKey:lg_valueForKey:方法,根据苹果官方文档提供的查找规则进行实现

@interface NSObject (LGKVC)
//设值
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key;
//取值
- (nullable id)lg_valueForKey:(NSString *)key;
@end
自定义KVC设值流程如下:
  • 1、判断key非空
  • 2、查找setter方法,顺序是:set<key>_set<key>setIs<key>
  • 3、判断是否响应accessInstanceVariablesDirectly方法,即间接访问实例变量,
    返回YES,继续下一步设值,
    如果是NO,则崩溃
  • 4、间接访问变量赋值(只会走一次),顺序是:_key、_isKey、key、isKey
    4.1 定义一个收集实例变量的可变数组
    4.2 通过class_getInstanceVariable方法,获取相应的ivar
    4.3 通过object_setIvar方法,对相应的ivar设置值
  • 5、如果找不到相关实例变量,则抛出异常
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key{
   
    // KVC 自定义
    // 1: 判断什么 key
    if (key == nil || key.length == 0) {
        return;
    }
    
    // 2: setter set<Key>: or _set<Key>,
    // key 要大写
    NSString *Key = key.capitalizedString;
    // 拼接方法
    NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
    NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
    NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
    
    if ([self lg_performSelectorWithMethodName:setKey value:value]) {
        NSLog(@"*********%@**********",setKey);
        return;
    }else if ([self lg_performSelectorWithMethodName:_setKey value:value]) {
        NSLog(@"*********%@**********",_setKey);
        return;
    }else if ([self lg_performSelectorWithMethodName:setIsKey value:value]) {
        NSLog(@"*********%@**********",setIsKey);
        return;
    }
    
    // 3: 判断是否响应 accessInstanceVariablesDirectly 返回YES NO 奔溃
    // 3:判断是否能够直接赋值实例变量
    if (![self.class accessInstanceVariablesDirectly] ) {
        @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }
    
    // 4: 间接变量
    // 获取 ivar -> 遍历 containsObjct -
    // 4.1 定义一个收集实例变量的可变数组
    NSMutableArray *mArray = [self getIvarListName];
    // _<key> _is<Key> <key> is<Key>
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([mArray containsObject:_key]) {
        // 4.2 获取相应的 ivar
       Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        // 4.3 对相应的 ivar 设置值
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:_isKey]) {
       Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:key]) {
       Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:isKey]) {
       Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }
    
    // 5:如果找不到相关实例
    @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];    
}
自定义KVC取值流程如下:
  • 1、判断key非空
  • 2、查找相应方法,顺序是:get<Key><key>countOf<Key>objectIn<Key>AtIndex
  • 3、判断是否能够直接赋值实例变量,即判断是否响应accessInstanceVariablesDirectly方法
    返回YES,继续下一步取值
    如果是NO,则崩溃
  • 4、间接访问实例变量,顺序是:_<key> _is<Key> <key> is<Key>
    4.1 定义一个收集实例变量的可变数组
    4.2 通过class_getInstanceVariable方法,获取相应的ivar
    4.3 通过object_getIvar方法,返回相应的ivar的值
- (nullable id)lg_valueForKey:(NSString *)key{
    
    // 1:刷选key 判断非空
    if (key == nil  || key.length == 0) {
        return nil;
    }

    // 2:找到相关方法 get<Key> <key> countOf<Key>  objectIn<Key>AtIndex
    // key 要大写
    NSString *Key = key.capitalizedString;
    // 拼接方法
    NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
    NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
    NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
        
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    }else if ([self respondsToSelector:NSSelectorFromString(key)]){
        return [self performSelector:NSSelectorFromString(key)];
    }else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
        if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
            int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
            for (int i = 0; i<num-1; i++) {
                num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            }
            for (int j = 0; j<num; j++) {
                id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
                [mArray addObject:objc];
            }
            return mArray;
        }
    }
#pragma clang diagnostic pop
    
    // 3:判断是否能够直接赋值实例变量
    if (![self.class accessInstanceVariablesDirectly] ) {
        @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }
    
    // 4.找相关实例变量进行赋值
    // 4.1 定义一个收集实例变量的可变数组
    NSMutableArray *mArray = [self getIvarListName];
    // _<key> _is<Key> <key> is<Key>
    // _name -> _isName -> name -> isName
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([mArray containsObject:_key]) {
        Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:_isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:key]) {
        Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }

    return @"";
}

相关文章

  • KVC分析

    KVC是什么? kvc 是key value coding 的缩写,在ios中用于通过key,来获取value,即...

  • KVC分析

    KVO初探 根据官方文档我们来验证一下 set方法原文 1、两个方法要是同时存在会先找找setName方法,要是没...

  • KVC分析

    官方解释什么是KVC 翻译过来就是: 键值编码(Key-value coding)是由 NSKeyValueCod...

  • KVC分析

    methodswizzling面试题 method-swizzling的含义是方法交换,其主要作用是在运行时将一个...

  • iOS-KVC相关

    KVC相关 一、 iOS成员变量,实例变量,属性变量的区别 二、KVC取值、赋值原理 *学习方式:1、分析源码 -...

  • iOS基础全面分析之一(KVC全面分析)

    iOS基础全面分析之一(KVC全面分析)iOS基础全面分析之二(RunLoop全面分析)iOS基础全面分析之三(K...

  • iOS基础全面分析之二(RunLoop全面分析)

    iOS基础全面分析之一(KVC全面分析)iOS基础全面分析之二(RunLoop全面分析)iOS基础全面分析之三(K...

  • iOS基础全面分析之三(KVO全面分析)

    iOS基础全面分析之一(KVC全面分析)iOS基础全面分析之二(RunLoop全面分析)iOS基础全面分析之三(K...

  • KVC原理分析

    KVC的使用 LGPerson对象有以下几个属性 我们可以通过setter方法直接进行赋值。 我们也可以通过KVC...

  • KVC原理分析

    一、KVC简介   KVC(Key-Value Coding)键值编码,是利用NSKeyValueCoding 非...

网友评论

      本文标题:KVC分析

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