键值编码(KVC)
KVC(Key Value Coding)是一种允许以字符串形式间接操作对象属性的方式。
最基本的KVC是由NSKeyValueCoding协议提供支持,最基本的操作属性如下:
- setValue: 属性值 forKey: 属性名:为指定属性设置值;
- valueForKey: 属性名:获取指定属性的值
代码演示:
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface AUser : NSObject@property (nonatomic, copy) NSString *str1;
@property (nonatomic, copy) NSString *str2;@endNS_ASSUME_NONNULL_END
#import <Foundation/Foundation.h>
#import "AUser.h"int main(int argc, const char * argv[]) {@autoreleasepool {AUser *aUser = [[AUser alloc] init];//使用KVC方式为str1属性设置值[aUser setValue:@"astr11" forKey:@"str1"];//使用KVC方式为str2属性设置值[aUser setValue:@"astr22" forKey:@"str2"];//使用KVC方式获取AUser对象的属性值NSLog(@"str1: %@", [aUser valueForKey:@"str1"]);NSLog(@"str2: %@", [aUser valueForKey:@"str2"]);}return 0;
}
结果:
在使用KVC时,都是通过字符串来指定被操作的属性。即使用forKey传入属性名的字符串。
对于setValue: forKey: 方法,底层的执行机制如下:
- 程序优先考虑调用属性的setter方法
- 如果该类没有setter方法,KVC机制会搜索该类中名为传入的“_该字符串”的成员变量(大部分时候即创建属性的时候自动创建的成员变量)无论该成员变量是在接口或者实现部分定义、无论它用哪个访问控制符修饰,这条KVC底层上是对该成员变量的赋值。
如果该类即没有setter方法也没有“_name”成员变量,那么KVC机制会搜索该类中名为name的成员变量(大部分时候即我们自己定义的成员变量)(与上条一样)
如果上面3步都没有找到,那么系统会执行该对象的setValue: forUndefinedKey:方法,该方法的实现就是引发一个异常,导致程序结束
valueForKey方法其他与上面一样,但是它获取的是getter方法的返回值。没有找到成员变量会执行valueForUndefinedKey:方法,该方法也会引起异常导致程序关闭。
代码举例:
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface AUser : NSObject {@packageNSString *name;NSString *_name;
}@endNS_ASSUME_NONNULL_END
#import "AUser.h"@implementation AUser {int age;
}
@end
#import <Foundation/Foundation.h>
#import "AUser.h"int main(int argc, const char * argv[]) {@autoreleasepool {AUser *aUser = [[AUser alloc] init];//使用KVC给属性赋值,KVC的搜素顺序为://1、setName方法;2、_name成员变量;3、name成员变量//因此,在此处我们是先搜索到了_name成员变量,所以是给_name赋了值,name没有赋值//因此name为空[aUser setValue:@"strName1" forKey:@"name"];NSLog(@"name = %@", aUser->name);NSLog(@"_name = %@", aUser->_name);//虽然age成员变量是在实现部分定义的,但是它还是会被赋值[aUser setValue: [NSNumber numberWithInt:5] forKey:@"age"];NSLog(@"age = %@", [aUser valueForKey:@"age"]);}return 0;
}
处理不存在的Key
前面说过,使用KVC时,如果该属性没有setter、getter方法,也不存在对应的成员变量时,程序会调用setValue: forUndefinedKey:或valueForUndefinedKey:方法。系统默认该方法的实现是引发一个异常然后结束程序,但是我们可以重写这个方法,使其达到我们想要的效果。
只需要在FKApple类实现部分重写setValue:forUndefinedKey:方法,甚至不需要在类接口
声明该方法,例:
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface AUser : NSObject {@packageNSString *name;NSString *_name;
}@endNS_ASSUME_NONNULL_END
#import "AUser.h"- (void) setValue: (id)value forUndefinedKey:(nonnull NSString *)key {NSLog(@"重写了setValue:value forUndefinedKey方法");
}@end
#import <Foundation/Foundation.h>
#import "AUser.h"int main(int argc, const char * argv[]) {@autoreleasepool {AUser *aUser = [[AUser alloc] init];[aUser setValue:@"strName1" forKey:@"1"];NSLog(@"name = %@", aUser->name);NSLog(@"_name = %@", aUser->_name);}return 0;
}
处理nil值
假如我们在一个类中定义两个属性,一个属性是NSString类型的,一个属性是int类型的。当我们给两个属性赋nil值时,NSString属性是可以被赋nil值的,而int类型的值被赋nil时会引发异常,是由于int类型的属性不能接受nil值所导致的。
也就是说,当程序尝试给某个属性设置nil值时,如果该属性并不能接受nil值,那么程序会自动执行该对象的setNilValueForKey:方法。我们同样可以重写该方法来达到我们想要的效果。例如,接下来我们重写该方法,定义一个int类型的属性age,重写该方法使得如果给age属性赋nil值时,就将age赋值为0。代码:
#import "AUser.h"@implementation AUser {int age;
}- (void) setNilValueForKey:(NSString *)key {//如果尝试将key为name的属性设置为nilif ([key isEqualToString:@"age"]) {//将_name设置为0age = 0;} else {//回调父类的setNilValueForKey:执行默认行为[super setNilValueForKey:key];}
}@end
#import <Foundation/Foundation.h>
#import "AUser.h"int main(int argc, const char * argv[]) {@autoreleasepool {AUser *aUser = [[AUser alloc] init];//使用KVC给age属性传nil[aUser setValue: nil forKey:@"age"];NSLog(@"age = %@", [aUser valueForKey:@"age"]);}return 0;
}
结果:
key路径
KVC除了可以操作对象的额属性之外,还可以操作对象的“复合属性”。所谓“复合属性”,KVC机制将其称为key路径。例如:AUser类里面包含着一个BUser类型的bUser属性,bUser对象中又包含着b1属相和b2属性,那么KVC可以通过bUser.b1、bUser.b2这种key路径来支持操作AUser对象的bUser属性的b1和b2属性。
根据key路径设置属性值的方法:
- setValue: forKeyPath:根据key路径设置属性值
- valueForKeyPath: 根据key路径获取属性值
代码示例:
AUser:
#import <Foundation/Foundation.h>
#import "BUser.h"NS_ASSUME_NONNULL_BEGIN@interface AUser : NSObject {@packageBUser *bUser;
}@property (nonatomic, assign) int aNumber;@endNS_ASSUME_NONNULL_END
BUser:
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface BUser : NSObject@property (nonatomic, copy) NSString *b1;
@property (nonatomic, copy) NSString *b2;@endNS_ASSUME_NONNULL_END
#import <Foundation/Foundation.h>
#import "AUser.h"
#import "BUser.h"int main(int argc, const char * argv[]) {@autoreleasepool {AUser *aUser = [[AUser alloc] init];[aUser setValue:@"12" forKey:@"aNumber"];[aUser setValue:[[BUser alloc] init] forKey:@"bUser"];[aUser setValue:@"这是b1" forKeyPath:@"bUser.b1"];[aUser setValue:@"这是b2" forKeyPath:@"bUser.b2"];NSLog(@"aNumber: %@", [aUser valueForKey:@"aNumber"]);NSLog(@"b1: %@", [aUser valueForKeyPath:@"bUser.b1"]);NSLog(@"b2: %@", [aUser valueForKeyPath:@"bUser.b2"]);}return 0;
}
结果:
实际上,通过KVC操作对象的性能比通过getter、setter方法操作的性能更差,使用KVC的优点是编程更加灵活,更适合提炼一些通用性质的代码