键值编码KVC与键值监听KVO

键值编码KVC与键值监听KVO

键值编码KVC

设置和获取对象的属性值有三种:setter/getter方法、点语法、KVC。KVC即Key Value Coding,以字符串形式间接操作对象的属性。
最基本的KVC由NSKeyValueCoding协议提供支持,最基本的操作属性的两个方法如下:

  • setValue:属性值 forKey:属性名:为指定属性设置值。
  • valueForKey:属性名:获取指定属性的值。
#import <Foundation/Foundation.h>

@interface FKUser : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSString* pass;
@property (nonatomic, copy) NSDate* birth;
@end

@implementation FKUser
@end

int main(int argc, char* argv[]){
    @autoreleasepool{
        FKUser* user = [[FKUser alloc] init];
        //使用KVC方式为name、pass、birth属性设置值
        [user setValue:@"wukong.sun" forKey:@"name"];
        [user setValue:@"1455" forKey:@"pass"];
        [user setValue:[[NSDate alloc] init] forKey:@"birth"];
        //使用KVC方式获取FKUser对象的属性值
        NSLog(@"user's name is %@", [user valueForKey:@"name"]);
        NSLog(@"user's pass is %@", [user valueForKey:@"pass"]);
        NSLog(@"user's date is %@", [user valueForKey:@"birth"]);
    }
}

编译执行以上程序,输出内容如下:

user's name is wukong.sun
user's pass is 1455
user's date is 2022-07-28 12:35:09 +0000

KVC的底层执行机制

以对上述程序中FKUser类对象的name属性采取KVC方式操作为例:

setValue:属性值 forKey:@“name”valueForKey:@“name”
优先考虑调用setName:属性值;代码通过setter方法完成设置优先考虑调用name;代码通过getter方法获取属性值
若没有setName:方法,KVC对_name成员变量赋值若没有name方法,KVC返回_name成员变量的值
既没有setName:方法,又没有_name成员变量,KVC对name成员变量赋值既没有name方法,又没有_name成员变量,KVC返回name成员变量的值
上三步都没有找到,系统会执行对象的setValue: forUndefinedKey:方法上三步都没有找到,系统会执行对象的valueForUndefinedKey:方法

默认的setValue: forUndefinedKey:方法和valueForUndefinedKey:方法是引发一个异常,这个异常会使程序因异常而结束。

#import <Foundation/Foundation.h>

@interface FKDog : NSObject{
    @package
    NSString* name;
    NSString* _name;
}
@end

@implementation FKDog{
    int age;
}
@end

int main(int argc, char* argv[]){
    @autoreleasepool{
        FKDog* dog = [[FKDog alloc] init];
        [dog setValue:@"旺财" forKey:@"name"];
        NSLog(@"dog->name:%@", dog->name);
        NSLog(@"dog->_name:%@", dog->_name);
        [dog setValue:[NSNumber numberWithInt:5] forKey:@"age"];
        NSLog(@"dog's age is %@", [dog valueForKey:@"age"]);
    }
}

以上程序输出:

dog->name:(null)
dog->_name:旺财
dog's age is 5

处理不存在的key

以KVC方式操作属性时,属性可能并不存在,即既没有对应的setter/getter方法。也没有对应的成员变量,这是KVC会自动调用setValue: forUndefinedKey:方法和valueForUndefinedKey:方法,引发异常而使程序结束。但实际业务中可能并不想程序因此结束,为了避免这种情况,可以重写这两个方法。

只需在类的实现部分重写这两个方法,不需要在类接口部分声明它们。

处理nil值

以KVC方式操作属性时,如果属性的类型是基本数据类型(如int、float、double),且程序传入了对应的值,那么KVC可以正确地进行设置。但如果尝试为基本类型的属性设置一个nil,程序会引发NSInvalidArgumentException异常而出现错误。当程序尝试为某个属性设置nil值,而该属性并不支持接受nil值时,程序就会自动执行该对象的setNilValurForKey:方法,这个方法默认会触发NSInvalidArgumentException异常。为了避免这种情况,可以重写setNilValurForKey:方法。

key路径

KVC除了可以操作对象的属性,还可操作对象的“复合属性”。比如A对象内包含一个B类型的b对象,B对象有b1、b2两个属性,那么KVC可以通过b.b1b.b2这种key路径来操作A对象的b属性的b1、b2属性。
KVC协议中操作key路径的方法如下:

  • setValue:forKeyPath::根据key路径设置属性值。
  • valueForKeyPath::根据key路径获取属性值。

键值监听KVO

iOS应用通常把应用程序组件分为数据模型组件视图组件,其中数据模型组件负责维护应用程序的状态数据,而视图组件负责显示数据模型组件内部的数据。

考虑以下需求:在数据模型组件的数据发生改变时,视图组件能动态地更新自己,及时显示数据模型组件的最新数据。

这时候,KVO就派上了用场。
KVO机制由NSKeyValueObserving协议提供支持,由于NSObject类遵守该协议,因此所有的Objective-C类都可使用该协议中的方法。该协议包含了如下常用方法用于注册监听器。

  • addObserver:forKeyPath:options:context::注册一个监听器用于监听指定的key路径。
  • removeObserver:forKeyPath::为key路径删除指定的监听器。
  • removeObserver:forKeyPath:context::为key路径删除指定的监听器。只是多了一个context参数。

对于上面的需求,很容易想到如下解决方案:让视图组件监听数据模型组件的改变,当数据模型组件的key路径对应的属性值发生改变时,作为监听器的视图组件将被触发,触发时就会回调监听器自身的监听方法。

#import <Foundation/Foundation.h>

@interface FKModel : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) int price;
@end

@implementation FKModel
@end

@interface FKView : NSObject
@property (nonatomic, weak) FKModel* model;
- (void) showModelInfo;
@end

@implementation FKView
- (void) showModelInfo{
    NSLog(@"name is %@, price is %d", self.model.name, self.model.price);
}
//自定义setModel:方法
- (void) setModel:(FKModel*) model{
    self->_model = model;
    //为model添加监听器,监听model的name属性的改变
    [self.model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    //为model添加监听器,监听model的price属性的改变
    [self.model addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionNew context:nil];
}
//重写该方法,当被监听的数据模型组件发生改变时,就会回调监听器的该方法
- (void) observeValueForKeyPath:(NSString*) keyPath ofObject:(id) object change:(NSDictionary*) change context:(void*) context{
    NSLog(@"--observeValueForKeyPath方法被调用--");
    NSLog(@"被修改的keyPath为:%@", keyPath);
    NSLog(@"被修改的对象为:%@", object);
    NSLog(@"被修改的属性值为:%@", [change objectForKey:@"new"]);
    NSLog(@"被修改的上下文为:%@", context);
}
- (void) dealloc{
    //删除监听器
    [self.model removeObserver:self forKeyPath:@"name"];
    [self.model removeObserver:self forKeyPath:@"price"];
}
@end

int main(int argc, char* argv[]){
    @autoreleasepool{
        FKModel* model =  [[FKModel alloc] init];
        model.name = @"疯狂iOS讲义";
        model.price = 99;
        FKView* view = [[FKView alloc] init];
        view.model = model;
        [view showModelInfo];
        //更改model对象的属性,将会触发监听器的方法
        model.name = @"疯狂XML讲义";
        model.price = 66;
    }
}

编译执行以上程序,输出如下:

name is 疯狂iOS讲义, price is 99
--observeValueForKeyPath方法被调用--
被修改的keyPath为:name
被修改的对象为:<FKModel: 0x13de11dd0>
被修改的属性值为:疯狂XML讲义
被修改的上下文为:(null)
--observeValueForKeyPath方法被调用--
被修改的keyPath为:price
被修改的对象为:<FKModel: 0x13de11dd0>
被修改的属性值为:66
被修改的上下文为:(null)

这样就实现了前面的需求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客熊猫GeekPanda

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值