iOS中KVC及KVO的简单理解

一、KVC及KVO的介绍

KVC:即Key-Value-Coding,用于键值编码。
KVO:即Key-Value-Observing,用于键值监听。

KVC:

  • 只针对类属性,设置键值对
  • 设置 setValue: forKey: ,即forKey只能为类属性
  • 取值 valueForKey

一个非正式的Protocol,提供一种机制来间接访问对象的属性。而不是通过调用Setter、Getter方法访问。
无论是Swift还是Objective-C,KVC的定义都是对NSObject的扩展来实现的(Objective-c中有个显式的NSKeyValueCoding类别名,而Swift没有,也不需要)所以对于所有继承了NSObject在类型,都能使用KVC(一些纯Swift类和结构体是不支持KVC的),下面是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来设值

KVC可以在运行时动态的访问和修改对象的属性,而不是在编译时确定。允许通过Key值访问对象属性,或者给对象赋值。

KVO:
KVO是Objective-C对观察这设计模式的一种实现。(另一种是:通知机制(notification))。
KVO提供一种机制,指定一个被观察对象,当对象某个属性发生改变时,对象会获得通知。当观察某对象A时,KVO机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性keyPath的setter 方法。setter 方法随后负责通知观察对象属性的改变状况。

  • 利用KVC对类属性进行设置
  • 注册observing对象** addObserver:forKeyPath:options:context:**
  • 观察者必须重写方法 observeValueForKeyPath:ofObject:change:context:

KVO是建立在KVC之上的,KVO能够观察一个对象的KVC key-path值的变化。

二、KVC及KVO的使用

<一>、kvc的使用

KVC在内部寻找key的是顺序:

  1. KVC机制,程序优先找set方法,set
  2. 如果没有set方法,** accessInstanceVariablesDirectly 方法如果返回Yes,KVC会执行setValue:forUNdefinedKey:方法,找成员变量_(默认返回YES)如果重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUNdefinedKey:**方法
  3. 如果即没有set:方法,也没有_成员变量,KVC机制会搜索is的成员变量,,如果该类即没有set:方法,也没有和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值。
  4. 如果都没有,则会执行setValue:forUNdefinedKey:方法
#import "ViewController.h"
@interface ViewController ()
{
    NSString *testNoSetFunc;
}
@property (nonatomic, strong) NSString *testStr;//默认生成成员变量及get、set方法

@end

@implementation ViewController

//accessInstanceVariablesDirectly重写
+ (BOOL)accessInstanceVariablesDirectly{
    //默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索

    return NO;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *test;//既没有成员变量也没有setter方法

    //fuzhi
    [self setValue:@"测试" forKey:@"test"];
    [self setValue:@"测试" forKey:@"testNoSetFunc"];
    [self setValue:@"测试" forKey:@"testStr"];
    
    //取值
    NSString *getTestNoSetFunc = [self valueForKey:@"testNoSetFunc"];
    NSString *getTest = [self valueForKey:@"test"];
    NSString *getTestStr = [self valueForKey:@"testStr"];

}

-(id)valueForUndefinedKey:(NSString *)key{
    NSLog(@"该key不存在%@",key);
    return nil;
}
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"该key不存在%@",key);
}

@end

如果一个类的成员变量是其他自定义类 可用valueForKeyPath 及
setValue:forKeyPath取值、赋值
valueForKeyPath
[类名 valueForKeyPath:@"类对象.类属性"];
setValue:forKeyPath:
[类名 setValue:@"值" forKeyPath:@"类对象.类属性"];

使用场景

<一>、kvc的使用

KVC是基于运行时的编程,极大的提高了灵活性,简化了代码。
(1)、动态地取值和赋值
(2)、用KVC来访问和修改私有变量
==对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的
(3)、Model和字典转换
(4)、修改一些控件的内部属性

    //1.添加textfield
    UITextField *textField = [[UITextField alloc]init];
    [self.view addSubview:textField];
    textField.frame = CGRectMake(10, 100, self.view.frame.size.width - 20, 30);
    //2.利用KVC修改placeholder的颜色
    textField.placeholder = @"请输入";
    [textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
<二>、kvo的使用

如果赋值没有通过setter方法或者KVC,而是直接修改属性对应的成员变量,例如:仅调用_name = @"newName",这时是不会触发kvo机制,更加不会调用回调方法的。
所以使用KVO机制的前提是遵循 KVO 的属性设置方式来变更属性值。

  1. 注册观察者,实施监听;
  2. 在回调方法中处理属性发生的变化;
  3. 移除观察者
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSString *name;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.name = @"测试";
    //1.注册通知
    [self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

}
//2.监听值改变
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    
    if ([keyPath isEqualToString:@"name"]&&object == self) {
        //
        self.view.backgroundColor = [UIColor redColor];
        NSLog(@"name的新值%@",[change valueForKey:@"new"]);
        NSLog(@"name的旧值%@",[change valueForKey:@"old"]);

    }
    
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    self.name = @"更改name的值";
}
//3.移除通知
- (void)dealloc{
    [self removeObserver:self forKeyPath:@"name"];
}
@end

KVO可监听自定义类属性的变化

//testModel.h
#import 

@interface testModel : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *number;

@end

//ViewController.m
#import "ViewController.h"
#import "testModel.h"
@interface ViewController ()
@property (nonatomic, strong) testModel *model;

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];

    testModel *model = [[testModel alloc]init];
    self.model = model;
    model.name = @"测试";
    //1.注册通知
    [self.model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];


}
//2.监听值改变
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    
    if ([keyPath isEqualToString:@"name"]&&object == self.model) {
        //
        self.view.backgroundColor = [UIColor redColor];
        NSLog(@"name的新值%@",[change valueForKey:@"new"]);
        NSLog(@"name的旧值%@",[change valueForKey:@"old"]);

    }
    
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    if (![self.model.name isEqualToString:@"更改name的值"]) {
        self.model.name = @"更改name的值";

    }
}
//3.移除通知
- (void)dealloc{
    [self.model removeObserver:self forKeyPath:@"name"];
}
@end

你可能感兴趣的:(iOS中KVC及KVO的简单理解)