iOS开发_KVO,KVC

本文详细介绍了KVC(键值编码)和KVO(键值观察)在iOS开发中的应用,包括具体案例、注意事项及底层实现原理。KVC主要用于为对象属性赋值和字典与模型之间的转换;KVO则提供了观察属性变化的功能,适用于同步model和view等场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2018.4.26提问:请描述一下你使用KVC,KVO的具体案例,以及你认为使用的时候需要注意的地方?


KVC:

具体案例:属性赋值,添加私有成员变量,字典转模型
注意的地方:

  • KVC,使用setValuesForKeysWithDictionary:方法,该方法默认根据字典中每个键值对,调用setValue:forKey方法
    缺点:
    -1. 字典中的键值对必须与模型中的键值对完全对应,否则程序会崩溃
  • 2.字段比较多的话,手写字典转模型就很累

解决方案:
- 1.使用一个第三方控件MJExtension;可以做到字典转模型,模型里面还可以套结模型,也可以套接模型数组,功能比较完善和强大。
- 2.处理- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
- 封装一个BaseModel 实现下面两个方法,创建如下转模型方法

// 将所有的数据转换为字符串
-(void)setValue:(id)value forKey:(NSString *)key{

    if([value isKindOfClass:[NSNull class]]){
        value=nil;
    }else if([value isKindOfClass:[NSArray class]]){
    }else{
        value = [NSString stringWithFormat:@"%@",value];
    }
    [super setValue:value forKey:key];
}

// 对特殊字符 id 进行处理
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"Undefined Key: %@", key);
}

// 字典转模型
-(id)initWithDic:(NSDictionary *)modelDic{

    self = [super init];
    if(self){
        [self setValuesForKeysWithDictionary:modelDic];
    }
    return self;

}

使用:

EmployeModel *aEmploye = [[EmployeModel alloc]initWithDic:employeeDic];

底层实现:
当一个对象调用setValue:forKey: 方法时,方法内部会做以下操作:

  • 1.判断有没有指定key的set方法,如果有set方法,就会调用set方法,给该属性赋值
  • 2.如果没有set方法,判断有没有跟key值相同且带有下划线的成员属性(_key).如果有,直接给该成员属性进行赋值

  • 3.如果没有成员属性_key,判断有没有跟key相同名称的属性.如果有,直接给该属性进行赋值

  • 4.如果都没有,就会调用 valueforUndefinedKey 和setValue:forUndefinedKey:方法

KVO:

具体案例:

  • KVO有显著的使用场景,当你希望监视一个属性的时候,当处理属性层的消息的事件时候,使用KVO,其他的尽量使用delegate。

优点:

  • 1.能够提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步;
  • 2.能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SDK对象)的实现;
  • 3.能够提供观察的属性的最新值以及先前值;
  • 4.用key paths来观察属性,因此也可以观察嵌套对象;
  • 5.完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察

缺点:

  • 1.我们观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查;
  • 2.对属性重构将导致我们的观察代码不再可用;
  • 3.复杂的“IF”语句要求对象正在观察多个值。这是因为所有的观察代码通过一个方法来指向;
  • 4.当释放观察者时不需要移除观察者。

注意事项:

  • 潜在的问题有可能出现在dealloc中对KVO的注销上。KVO的一种缺陷(其实不能称为缺陷,应该称为特性)是,当对同一个keypath进行两次removeObserver时会导致程序crash,这种情况常常出现在父类有一个kvo,父类在dealloc中remove了一次,子类又remove了一次的情况下。不要以为这种情况很少出现!当你封装framework开源给别人用或者多人协作开发时是有可能出现的,而且这种crash很难发现。不知道你发现没,目前的代码中context字段都是nil,那能否利用该字段来标识出到底kvo是superClass注册的,还是self注册的?
    回答是可以的。我们可以分别在父类以及本类中定义各自的context字符串,比如在本类中定义context为@”ThisIsMyKVOContextNotSuper”;然后在dealloc中remove
    observer时指定移除的自身添加的observer。这样iOS就能知道移除的是自己的kvo,而不是父类中的kvo,避免二次remove造成crash

KVC:键值编码

主要作用是:
(1)通过键值路径为对象的属性赋值。主要是可以为私有的属性赋值。

AppleViewController *appleVC = [[AppleViewController alloc]init];
[appleVC setValue:@"橘子" forKey:@"name"];

如果对象A的属性是一个对象B,要设置对象B的属性

[person setValue:@"旺财" forKeyPath:@"dog.name"];

(2)通过键值路径获取属性的值。主要是可以通过key获得私有属性的值。

 NSString *nameStr = [appleVC valueForKey:@"name"];

也可以通过keypath获得值

NSString *dName = [person valueForKeyPath:@"dog.name"];

(3)将字典转型成Model,方法:setValuesForKeysWithDictionary:

 // 定义一个字典
    NSDictionary *dict = @{
                           @"name"  : @"jack",
                           @"money" : @"20.7",
                           };
    // 创建模型
    Person *p = [[Person alloc] init];

    // 字典转模型
    [p setValuesForKeysWithDictionary:dict];
    NSLog(@"person's name is the %@",p.name);

注意:字典的key和Model的属性一定要一一对应。否则会出现错误。比如person里没有name的属性,系统报错如下:

‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.’

二. KVO

1.KVO:键值观察(key-value-observing)

KVO提供了一种观察者的机制,通过对某个对象的某个属性添加观察者,当该属性改变,就会调用”observeValueForKeyPath:”方法,为我们提供一个“对象值改变了!”的时机进行一些操作。

2.KVO原理
当某个类的对象第一次被观察时,系统在运行时会创建该类的派生类,改派生类中重写了该对象的setter方法,并且在setter方法中实现了通知的机制。派生类重写了class方法,以“欺骗”外部调用者他就是原先那个类。系统将这个类的isa指针指向新的派生类,因此改对象也就是新的派生类的对象了。因而改对象调用setter就会调用重写的setter,从而激活键值通知机制。此外派生类还重写了delloc方法来释放资源。

3.KVO的使用

(1)给对象的属性添加观察者

 [appleVC addObserver:self forKeyPath:@"name" options: NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];

注: options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld 返回未改变之前的值和改变之后的值 context可以为空

(2)若该属性发生改变,系统自动调用下面的方法:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    //拿到新值/旧值,进行操作
    NSLog(@"newValue----%@",change[@"new"]);
    NSLog(@"oldValue----%@",change[@"old"]);
}

(3)取消监听

-(void)dealloc
{
    [person removeObserver:self forKeyPath:@"test"];
}

4.KVO的使用场景

KVO用于监听对象属性的改变。

  (1)下拉刷新、下拉加载监听UIScrollView的contentoffsize;
  (2)webview混排监听contentsize;
  (3)监听模型属性实时更新UI;
  (4)监听控制器frame改变,实现抽屉效果。

#import "ViewController.h"
#import "Person.h"

@interface ViewController ()
@property(nonatomic,strong)Person * p;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _p =[[Person alloc]init];
    //添加观察者
    [_p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"观察到了");
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    _p.name =@"hank";
}

成员变量是对成员属性的一个封装,每个成员变量都包含_name + getter + setter 方法

如果我们自己一个成员属性,那么使用observeValueForKeyPath就观察不到值的变化,所以我们可以知道,观察者观察的是setter方法。

#import <Foundation/Foundation.h>

@interface Person : NSObject{
    @public
    NSString * _name;
}

//_name + getter + setter 方法!
//@property(nonatomic,copy)NSString * name;

@end

ViewController.h

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//    _p.name =@"hank";
    _p->_name=@"hank";
}

KVO的底层实现原理:1.动态创建一个类NSKVONotyfing_Person,继承自被继承的类Person,重写setter方法
2.改变P对象的类型!(子类类型)

#import "NSKVONotyfing_Person.h"

@implementation NSKVONotyfing_Person

-(void)setName:(NSString *)name{
    [self willChangeValueForKey:@"name"];
    [super setName:name];
    [self didChangeValueForKey:@"name"];
}
@end
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值