KVC&KVO
一,KVC(Key-value coding)键值编码,可以在运行时动态地访问和修改对象的属性。
1,KVC四个重要方法:
- (nullable id)valueForKey:(NSString *)key; //直接通过Key来取值
- (void)setValue:(nullable id)value forKey:(NSString *)key; //通过Key来设值
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过KeyPath来设值
2,KVC类别中其他常用方法:
+ (BOOL)accessInstanceVariablesDirectly;
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。需要手动调用
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
- (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//和上一个方法一样,但这个方法是设值。
- (void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法,把一个 nil 值赋值给纯量(如 float, int, …)时,setNilValueForKey会被调用,默认抛出异常
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString*,id>*)keyedValues;
//输入一个字典,为当前数据模型相应的KEY进行赋值
二,KVC相关原理
1,set<Key>底层调用顺序:先查找是否有如果没有找到Set<Key>方法的话,则检查accessInstanceVariablesDirectly,如果返回为NO,直接执行setValue:forUndefinedKey:方法;如果返回为YES,会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作。
2,当调用valueForKey进行取值时的调用顺序:
(1)先按get<Key>、<key>、is<Key>方法按顺序进行查找,找到直接调用;
(2)查找countOf<Key>和(objectIn<Key>AtIndex或<Key>AtIndexes中的一个)格式的方法;如果都找到,则返回一个响应NSArray所有方法的代理集合
(3)同时查找countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的方法,如果都找到,则返回一个响应NSSet所有方法的代理集合
(4)如果以上都没找到,则检查accessInstanceVariablesDirectly,如果返回为NO,直接执行valueForUndefinedKey
:方法;如果返回为YES,会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作。
3,需要注意的是我们不能直接将一个数值通过KVC进行赋值,而需要将数值转为NSNumber或NSValue;而NSValue主要用于处理结构体类型的数据,如CGSize,CGPoint等,或者其它自定义结构体。
三,KVC集合处理
1,简单运算符有:@avg, @count , @max , @min ,@sum5种,对指定属性内容进行相应的操作,不支持自定义。
2,对象运算符(返回指定属性内容的数组):@distinctUnionOfObjects(去重后的结果)、@unionOfObjects(指定内容的全部元素)。
@interface People : NSObject
@property (nonatomic, assign) NSUInteger age;
@property (nonatomic, copy) NSString *name;
@end
@implementation People
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
People *p1 = [People new];
p1.name = @"a";
p1.age = 12;
People *p2 = [People new];
p2.name = @"c";
p2.age = 12;
People *p3 = [People new];
p3.name = @"b";
p3.age = 13;
NSArray *peoples = @[p1,p2,p3];
NSArray *disPeoples = [peoples valueForKeyPath:@"@distinctUnionOfObjects.age"];
NSArray *nPeoples = [peoples valueForKeyPath:@"@unionOfObjects.age"];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
四,KVC使用场景
1,进行动态取值和赋值,常用来访问和个性私有变量或控件的内部属性
2,Model和字典之间的相互转换
3,用操作集合来操作NSArray和NSSet这样的容器
4,用KVC实现高阶消息传递:当对容器类使用KVC时,valueForKey:将会被传递给容器中的每一个对象,而不是容器本身进行操作。结果会被添加进返回的容器中,这样,开发者可以很方便的操作集合来返回另一个集合。
NSArray* arrStr = @[@"english",@"franch",@"chinese"];
NSArray* arrCapStr = [arrStr valueForKey:@"capitalizedString"];
for (NSString* str in arrCapStr) {
NSLog(@"%@",str);
}
NSArray* arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"];
for (NSNumber* length in arrCapStrLength) {
NSLog(@"%ld",(long)length.integerValue);
}
五,KVO的使用
KVO 即 Key-Value Observing,需要继承NSObject才可使用。主要是用于监听对象的某个key值来获得相应的Value值,用来监听状态的变化。
当监听key时,可通过访问器触发,也可通过KVC中的setValue触发,如果直接设置类的成员变量是不会引起触发的。
1,通往以下方法进行注册和解除注册:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
2,通过以下方法监听状态的变化:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
2.1 params解读
observer:观察者,也就是KVO通知的订阅者。订阅着必须实现
observeValueForKeyPath:ofObject:change:context:方法
keyPath:描述将要观察的属性,相对于被观察者。
options:KVO的一些属性配置;有四个选项。
context: 上下文,这个会传递到订阅着的函数中,用来区分消息,所以应当是不同的。
2.2 options所包括的内容
NSKeyValueObservingOptionNew:change字典包括改变后的值
NSKeyValueObservingOptionOld:change字典包括改变前的值
NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知
NSKeyValueObservingOptionPrior:值改变前是否也要通知(这个key决定了是否在改变前改变后通知两次)
3,禁止自动KVO及手动调用KVO
通过将automaticallyNotifiesObserversForKey设置某个key的返回为NO可禁止某个Key进行自动KVO,然后在其setter方法中通过如下方法实现手动调用:
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
六,使用KVO设置键值观察依赖键
使用场景:一个对象的属性之间是相互关联的,也就是对象的一个属性依赖于另一个对象的一个获多个属性。如果这些被依赖属性中的任意一个值发生改变,那么原属性的值也会发生相应的改变。
1,Student跟Person类定义
@interface Person : NSObject
@property (nonatomic, copy) NSString *personName;
@property (nonatomic, assign) NSInteger personAge;
@end
@interface Student : NSObject
@property (nonatomic, copy) NSString *infomation;
@property (nonatomic, strong) Person *person;
@end
@implementation Person
- (instancetype)init{
if(self = [super init]){
self.personAge = 10;
self.personName = @"Alro";
}
return self;
}
@end
@implementation Student
- (instancetype)init{
if(self = [super init]){
_person = [[Person alloc] init];
}
return self;
}
- (NSString*)infomation{
return [NSString stringWithFormat:@"the student infomation is:name=%@ | age=%ld",self.person.personName,self.person.personAge];
}
- (void)setInfomation:(NSString *)infomation{
NSArray *infoArr = [infomation componentsSeparatedByString:@"#"];
if(infoArr.count == 2){
self.person.personName = [infoArr objectAtIndex:0];
self.person.personAge = [[infoArr objectAtIndex:1] integerValue];
}
}
+ (NSSet<NSString*>*)keyPathsForValuesAffectingInfomation{
return [NSSet setWithObjects:@"person.personName",@"person.personAge", nil];
}
//+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
// if ([key isEqualToString:@"infomation"]) {
// NSSet *set = [NSSet setWithObjects:@"person.personName", @"person.personAge",nil];
// return set;
// }
// return nil;
//}
@end
2,调用
@interface ViewController ()
@property (nonatomic, strong) Student *student;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_student = [[Student alloc] init];
[_student setInfomation:@"lucy#5"];
[_student addObserver:self forKeyPath:@"infomation" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
_student.person.personName = @"major";
_student.person.personAge = 35;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if([keyPath isEqualToString:@"infomation"]){
NSLog(@"OBSERVE : old infomation = %@ | new infomation = %@", change[NSKeyValueChangeOldKey], change[NSKeyValueChangeNewKey]);
}
}
@end
七,KVO与线程
KVO 行为是同步的,并且发生与所观察的值发生变化的同样的线程上。所以通常不推荐把 KVO 和多线程混起来,除非能确定所有的观察者都用线程安全的方法处理 KVO 通知。
参考:https://www.jianshu.com/p/875dec26e472
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术