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 (nonatomicassignNSUInteger age;

@property (nonatomiccopyNSString *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

posted @   Major1698  阅读(79)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示