文章

KVC

日常开发在哪用到了?

最常见的是在 model 里(概念: 键值编码是一种由 NSKeyValueCoding 非正式协议启用的机制,对象通过采用该机制为其属性提供间接访问。当一个对象符合键值编码规范时,可以通过字符串参数,通过简洁、统一的消息接口访问其属性。这种间接访问机制补充了实例变量及其关联的访问器方法提供的直接访问。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import "TestModel.h"

@implementation TestModel

+ (instancetype)modelWithDict:(NSDictionary *)dict {
    TestModel *model = [[TestModel alloc] init];
    [model setValuesForKeysWithDictionary:dict];

    return model;
}

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {};

@end

通过几个问题来理解它的方法

问题 1

  • 下面的代码会崩溃吗,如果不会,控制台会打印什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface TestModel : NSObject

@property(nonatomic, assign) NSInteger age;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    TestModel *model = [[TestModel alloc] init];
    [model setValue:@"11岁" forKey:@"age"];
    NSLog(@"model.age: %ld", model.age);
}

@end
  • 答案:不会崩溃,控制台打印
1
model.age: 11
  • 原因:我们看苹果官方文档,或者看 xcode 里方法上面的注释: 如果是其他类型(非对象类型),会做一个 NSNumber/NSValue 的逆转换。
1
2
3
If the type of the method's parameter is some other type
the inverse of the NSNumber/NSValue conversion done by -valueForKey:
is performed before the method is invoked.

问题 2

  • 控制台打印什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@interface TestModel : NSObject

@property(nonatomic, assign) NSInteger age;

@end

#import "TestModel.h"

@implementation TestModel

- (void)setAge:(NSInteger)age {
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    TestModel *model = [[TestModel alloc] init];
    [model setValue:@(18) forKey:@"age"];
    NSLog(@"model.age: %ld", model.age);
}

@end

  • 答案:
1
model.age: 0
  • 原因:官方文档 Search Pattern for the Basic Setter第一点
1
2
3
4
5
1、Look for the first accessor named set<Key>: or \_set<Key>, in that order. If found, invoke it with the input value (or unwrapped value, as needed) and finish.

2、If no simple accessor is found, and if the class method accessInstanceVariablesDirectly returns YES, look for an instance variable with a name like \_<key>, \_is<Key>, <key>, or is<Key>, in that order. If found, set the variable directly with the input value (or unwrapped value) and finish.

3、Upon finding no accessor or instance variable, invoke setValue:forUndefinedKey:. This raises an exception by default, but a subclass of NSObject may provide key-specific behavior.

问题 3

  • 控制台打印什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@interface TestModel : NSObject

{
@public
    NSInteger age;
    NSInteger _isAge;
}

@end


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    TestModel *model = [[TestModel alloc] init];
    [model setValue:@(18) forKey:@"age"];
    NSLog(@"model.age: %ld", model->age);
    NSLog(@"model._isAge: %ld", model->_isAge);
}

@end

  • 答案:
1
2
model.age: 0
model._isAge: 18
  • 原因:官方文档 Search Pattern for the Basic Setter第二点
1
2
3
4
5
1、Look for the first accessor named set<Key>: or \_set<Key>, in that order. If found, invoke it with the input value (or unwrapped value, as needed) and finish.

2、If no simple accessor is found, and if the class method accessInstanceVariablesDirectly returns YES, look for an instance variable with a name like \_<key>, \_is<Key>, <key>, or is<Key>, in that order. If found, set the variable directly with the input value (or unwrapped value) and finish.

3、Upon finding no accessor or instance variable, invoke setValue:forUndefinedKey:. This raises an exception by default, but a subclass of NSObject may provide key-specific behavior.

思考

  • 崩溃的原因是?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface TestModel : NSObject

@property(nonatomic, assign) NSInteger age;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    TestModel *model = [[TestModel alloc] init];
    [model setValue:[NSNull null] forKey:@"age"];
    NSLog(@"model.age: %ld", model.age);
}

@end
  • 在做逆转换的时候,需要调用 longLongValue, 而 NSNull 没有这个实例方法,就会崩溃 "-[NSNull longLongValue]: unrecognized selector sent to instance
本文由作者按照 CC BY 4.0 进行授权