OC底层原理(12)-KVC

KVC初探

1.通常setter方法

代码:数组

ViewController.mbash

person.name      = @"LG_Cooci";
 person.age       = 18;
 person->myName   = @"cooci";
 NSLog(@"%@ - %d - %@",person.name,person.age,person->myName);
复制代码

打印结果:markdown

Janice - 18 - ty
复制代码

2.Key-Value Coding (KVC) : 基本类型赋值

代码:atom

ViewController.mspa

[person setValue:@"Janice" forKey:@"name"];
[person setValue:@19 forKey:@"age"];
[person setValue:@"ty" forKey:@"myName"];
NSLog(@"%@ - %@ - %@",[person valueForKey:@"name"],[person valueForKey:@"age"],[person valueForKey:@"myName"]);
复制代码

打印结果:指针

Janice - 19 - ty
复制代码

3.KVC:集合类型赋值

代码:code

ViewController.morm

//不可变数组

person.array = @[@"1",@"2",@"3"];
NSArray *array = [person valueForKey:@"array"];
// 用 array 的值建立一个新的数组
array = @[@"100",@"2",@"3"];
[person setValue:array forKey:@"array"];
NSLog(@"%@",[person valueForKey:@"array"]);

//可变数组
NSMutableArray *ma = [person mutableArrayValueForKey:@"array"];
ma[0] = @"200";
NSLog(@"%@",[person valueForKey:@"array"]);
复制代码

打印结果:对象

(
    100,
    2,
    3
)

(
    200,
    2,
    3
)
复制代码

4.KVC - 访问非对象属性

代码:three

LGPerson.h

#import <Foundation/Foundation.h>
#import "LGStudent.h"

NS_ASSUME_NONNULL_BEGIN

typedef struct {
    float x, y, z;
} ThreeFloats;

@interface LGPerson

@property (nonatomic) ThreeFloats threeFloats;

@end
复制代码

ViewController.m

ThreeFloats floats = {1., 2., 3.};
    NSValue *value  = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
    [person setValue:value forKey:@"threeFloats"];
    NSValue *reslut = [person valueForKey:@"threeFloats"];
    NSLog(@"%@",reslut);
    
    ThreeFloats th;
    [reslut getValue:&th] ;
    NSLog(@"%f - %f - %f",th.x,th.y,th.z);
复制代码

打印结果:

{length = 12, bytes = 0x0000803f0000004000004040}
1.000000 - 2.000000 - 3.000000
复制代码

5.KVC - 层层访问

LGStudent *student = [[LGStudent alloc] init];
    student.subject    = @"语文";
    person.student     = student;
    [person setValue:@"数学" forKeyPath:@"student.subject"];
    NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);
复制代码

打印结果:

数学
复制代码

KVC原理设值过程

这个过程当中主要用成员变量来探索,为何要使用成员变量呢,由于属性在赋值的过程当中原本就会生成getter 和 setter 方法了,因此再用属性探索KVC赋值,会很差分辩。

根据官方文档可知 KVC setter 过程当中查找赋值的方法以下:

一、set<Key>: 或则 _set<key>

二、若是找不到第一步,查看 accessInstanceVariablesDirectly 返回是否为YES,若是返回YES,就继续查找一下方法:_<key>, _is<key>,<key>,或则 is<key>

依次来验证一下:

一、先找setter

代码:

LGPerson.h

#import <Foundation/Foundation.h>
#import "LGStudent.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject{
    @public
     NSString *_name;
     NSString *_isName;
     NSString *name;
     NSString *isName;
}
@end
复制代码

LGPerson.m

#import "LGPerson.h"

@implementation LGPerson

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

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

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

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

@end
复制代码

ViewController.m

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

    NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
    NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
    NSLog(@"%@-%@",person->name,person->isName);
    NSLog(@"%@",person->isName);
}
复制代码

运行结果:

二、 当 accessInstanceVariablesDirectly 为 YES

LGPerson.m 中代码稍微作一点改动,以下:

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

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

- (void)setIsName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}
复制代码

打印结果:

这里走的就是 _setName 方法

而后代码再作一点改动,以下

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

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

- (void)setIsName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}
复制代码

打印结果:

而后这里就调用了 setIsName

二、 当 accessInstanceVariablesDirectly 为 NO 时,作以下改动

#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
    return NO;
}

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

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

//- (void)setIsName:(NSString *)name{
//    NSLog(@"%s - %@",__func__,name);
//}
复制代码

打印结果:

崩溃了

以上,打印估计有注意到了我下面打印的值所有都为 null,这是由于在重写方法时,并无复制的哈,如_name = name;

下面将重写方法全都注释掉,以下,再来打印成员变量的值,再将 accessInstanceVariablesDirectly 而后YES。

代码:

LGPerson.m

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

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

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

//- (void)setIsName:(NSString *)name{
//    NSLog(@"%s - %@",__func__,name);
//}
复制代码

ViewController.m

#import "ViewController.h"
#import "LGPerson.h"

@interface ViewController ()

@end

@implementation ViewController

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

    NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
    NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
    NSLog(@"%@-%@",person->name,person->isName);
    NSLog(@"%@",person->isName);
}
复制代码

打印结果:

能够看出只有第一个成员有值,而后将第一个成员变量注释掉,并将第一个打印注释掉

打印: LGPerson.h

#import <Foundation/Foundation.h>
#import "LGStudent.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject{
    @public
//     NSString *_name;
     NSString *_isName;
     NSString *name;
     NSString *isName;
}
@end
复制代码

ViewController.m

#import "ViewController.h"
#import "LGPerson.h"

@interface ViewController ()

@end

@implementation ViewController

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

//    NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
    NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
    NSLog(@"%@-%@",person->name,person->isName);
    NSLog(@"%@",person->isName);
}
复制代码

打印结果:

再验证最后一个

#import <Foundation/Foundation.h>
#import "LGStudent.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject{
    @public
//     NSString *_name;
//     NSString *_isName;
//     NSString *name;
     NSString *isName;
}
@end
复制代码

ViewController.m

#import "ViewController.h"
#import "LGPerson.h"

@interface ViewController ()

@end

@implementation ViewController

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

//    NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
//    NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
//    NSLog(@"%@-%@",person->name,person->isName);
 //   NSLog(@"%@",person->isName);
}
复制代码

打印结果:

以上打印验证了只有,在编译期过程当中,原本只有一个name,可是还会生成 其余变量,如_name, 布尔值时,就有 isOpen,等,这就归功于 runtime的做用。

KVC原理取值过程

官方文档的查找流程以下,get<key>, <key>, is<key>,_<key>

代码:

LGPerson.h

@interface LGPerson : NSObject{
    @public
     NSString *_name;
     NSString *_isName;
     NSString *name;
     NSString *isName;
}
@end
复制代码

LGPerson.m

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

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

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

- (NSString *)_name{
    return NSStringFromSelector(_cmd);
}
复制代码

ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LGPerson *person = [[LGPerson alloc] init];
    // 2: KVC - 取值的过程
    
    //先赋值不一样的值
     person->_name = @"_name";
     person->_isName = @"_isName";
     person->name = @"name";
     person->isName = @"isName";
    
    //取值
     NSLog(@"取值:%@",[person valueForKey:@"name"]);
}
复制代码

打印结果:

打印了 getName 的方法名,说明在找变量以前先找的方法。

再验证一个,就将getName注释掉,走下一个方法

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);
}
复制代码

打印结果

KVC设置与取值总结:

KVC 设置过程

一、判断是否存在'set<key>' 或者 '_set<key>'(带下划线的属性)'setls<key>'

二、若是没有条件 1,(简单访问方式)

2.一、判断 'accessInstanceVariablesDirectly' 是否存在,返回 'YES'

2.二、判断 '_<key>','_is<key>','<key>','is<key>'等实例变量

2.三、直接给这些实例变量设置

三、'setValue:forUndefinedKey:'报错!

KVC 取值过程:

一、若是找到'get<key>',<key>,'is<key>','_<key>'这几个方法就跳到 '第五步'

二、若是没有条件 1 ,开始是不是 NSArray 判断

三、是不是 NSSet 判断

四、非集合类型

4.一、'accessInstanceVariablesDirectly' 返回 YES

4.二、'_<key>,_is<key>,,is<key>'这些实例属性

4.三、若是找到,直接获取实例变量的值,而后继续执行步骤 5

五、细节处理

5.一、若是检索到的属性值是对象指针,则只需返回结果

5.二、若是该值是 NSNumber 支持的标量类型,则将其存储在 NSNumber 实例中并返回它

5.三、若是结果是NSNumber 不支持的标量类型,则转换为 NSValue 对象并返回

六、'valueForUndefineKey'报错!!!

七、集合类型的还须要操做!

KVC使用小技巧

一、KVC 自动转换类型

如,

[person setValue:@"20" forKey:@"age"];
复制代码

age 为 int 类型,可是给了一个 NSString 类型,因而,便会自动转换类型为 __NSCFNumber

二、能够对int,NSValue 类型设置空值,对NSString设空值变不会走设置的任何方法

当找不到时能够用一下方法能够检测到

- (void)setNilValueForKey:(NSString *)key;
复制代码

三、找不到Key

当找不到时能够用一下方法能够检测到

- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
复制代码

四、取值时 - 找不到 key

当找不到时能够用一下方法能够检测到

- (nullable id)valueForUndefinedKey:(NSString *)key;
复制代码

五、键值验证

在此方法能够作一些容错处理

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
复制代码
相关文章
相关标签/搜索