如何理解iOS的KVO和KVC?

第一部分:KVO可以实现什么功能?

1.1 KVO 本质

    1. KVO 全称Key-Value-Observing
    1. KVO 观察一个对象的属性,注册一个指定的路径,若这个对象的的属性被修改,则 KVO 会自动通知观察者;KVO 是一个观察者模式
    1. KVO 只能对属性【对象下面的属性 】做出反应,不会用来对方法或者动作做出反应。
    1. 每一次属性值改变都是自动发送通知,不需要开发者手动实现。
    1. 注意:任何对象都允许观察其他对象的属性,并且可以接收其他对象状态变化的通知。
    1. 当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法会负责在调用原 setter方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。

1.2 KVO 例子

  • 例子1:

_person = [[Person alloc] init]; 
     
/** 
 *  添加观察者 
 * 
 *  @param observer 观察者 
 *  @param keyPath  被观察的属性名称 
 *  @param options  观察属性的新值、旧值等的一些配置(枚举值,可以根据需要设置,例如这里可以使用两项) 
 *  @param context  上下文,可以为nil。 
 */ 
[_person addObserver:self 
          forKeyPath:@"age" 
             options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld 
             context:nil]; 

/** 
 *  KVO 回调方法 
 * 
 *  @param keyPath 被修改的属性 
 *  @param object  被修改的属性所属对象 
 *  @param change  属性改变情况(新旧值) 
 *  @param context context 传过来的值 
 */ 
- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary<NSString *,id> *)change 
                       context:(void *)context 
{ 
    NSLog(@"%@ 对象的%@属性改变了:%@",object,keyPath,change); 
} 

/** 
 *  移除观察者 
 */ 
- (void)dealloc 
{ 
    [self.person removeObserver:self forKeyPath:@"age"]; 
} 


  • 例子2:监听 ScrollView 的 contentOffSet 属性

[scrollview addObserver:self 
             forKeyPath:@“contentOffset                    
                options:NSKeyValueObservingOptionNew 
                context:nil]; 
                

1.3 KVO 的语法


【注册】1.// 注册观察者,实施监听; 
[self.person addObserver:self 
              forKeyPath:@"age" 
                 options:NSKeyValueObservingOptionNew 
                 context:nil]; 

【观察】2.// 观察方法,回调方法,在这里处理属性发生的变化; 
- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary<NSString *,id> *)change 
                       context:(void *)context 

【移除】3.// 移除观察者; 
[self removeObserver:self forKeyPath:@“age"]; 

1.4 KVO与runtime

  • 1.当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。
  • 2.被观察对象的 isa 指针会指向一个中间类,而不是原来真正的类。

第二部分:KVC可以实现什么功能?

2.1 KVC 本质

    1. KVC 全称NSKeyValueCoding,中文:键值编码
    1. 通过字符串的名字(key)来访问类属性;
    1. 不通过调用Setter、Getter方法访问;
    1. 在运行时动态的访问和修改对象的属性,不是在编译时确定;
  • KVC 缺点

      1. 执行效率要低于 setter 和 getter 方法。因为使用 KVC 键值编码,它必须先解析字符串,然后在设置或者访问对象的实例变量。
      1. 使用 KVC 会破坏类的封装性。
      1. 难排查错误

2.2 功能1:对集合操作

  • 例子1:求最大值

NSArray *a = @[@4, @84, @2]; 
NSLog(@"max = %@", [a valueForKeyPath:@"@max.self"]); 

  • 例子2:有一个 Transaction 对象的数组,对象有属性 amount 的话;当我们调用 [a valueForKeyPath:@"@max.amount"] 的时候,它会在数组 a 的每个元素中调用 -valueForKey:@“amount” 然后返回最大的那个。

NSArray *a = @[transaction1, transaction2, transaction3]; 
NSLog(@"max = %@", [a valueForKeyPath:@"@max.amount"]); 

2.3 功能2:对私有变量{如readonly变量—如private变量}赋值

  • 修改系统的一些类的私有属性,必须先要拿到属性的名字,一般都是下划线开头的属性名
    1. 需改私有属性-例子1:

@interface Teacher : NSObject 
{ 
    @private int _age;// 私有变量,一般外部不能改变,通过kvc可以改变,前提你知道这个私有变量的名字; 
} 

@property (nonatomic, strong, readonly) NSString *name;// 只读变量,一个外部不能直接赋值,通过kvc可以改变 

@property (nonatomic, assign, getter = isMale) BOOL male; 

- (void)log; 

@end 


  • 使用kvc前: 用一般的 setter 和 getter,在类外部是不能访问到私有变量的,不能设值给只读变量
  • 使用kvc后:

Teacher *teacher = [Teacher new]; 
[teacher log]; 

// 设置 readonly value 
[teacher setValue:@"Jack" forKey:@"name"]; 
// teacher.name = @"Jack"; 
    
// 设置 private value 
[teacher setValue:@24 forKey:@"age"]; 
// teacher.age = 24; 
[teacher setValue:@1 forKey:@"male"]; 
[teacher log]; 

// 获取 readonly value 
NSLog(@"name: %@", [teacher valueForKey:@"_name"]); 
// 获取 private value 
NSLog(@"age: %d", [[teacher valueForKey:@"_age"] intValue]); 
NSLog(@"male: %d", [[teacher valueForKey:@"isMale"] boolValue]); 


    1. 需改私有属性-例子2:修改 TextField 的 placeholder: 注意这里用了keypath

[_textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];   
[_textField setValue:[UIFont systemFontOfSize:14] forKeyPath:@“_placeholderLabel.font"]; 


    1. 需改私有属性-例子3:修改 UIPageControl 的图片:

[_pageControl setValue:[UIImage imageNamed:@"selected"] forKeyPath:@"_currentPageImage"]; 
[_pageControl setValue:[UIImage imageNamed:@"unselected"] forKeyPath:@"_pageImage"]; 

2.4 功能3:字典转模型


- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues; 

2.4.1 kvc 语法覆盖

  • key 和keypath的区别?

    • key只能接受当前类所具有的属性,不管是自己的,还是从父类继承过来的,如view.setValue(CGRectZero(),key: “frame”);

    • keypath: 除了能接受当前类的属性,还能接受当前类属性的属性,即可以接受关系链,如view.setValue(5,keypath: "layer.cornerRadius”)

  • 举个例子说明问题1:

  • 例子:比如person有个属性是address,address有个属性是town,现在我们如何通过person访问town属性?

  • 答:如果通过key来访问


id address = [person valueForKey:@"address"]; 
id town = [address valueForKey:@"town”]; 
// 如果通过keypath来访问 
id town = [person valueForKeyPath:@"address.town"]; 


2.4.2 语法(设置值,设置数据)


// value 的值为OC对象,如果是基本数据类型要包装成NSNumber 
- (void)setValue:(id)value forKey:(NSString *)key; 

// keyPath 键路径,类型为xx.xx 
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath; 

// 它的默认实现是抛出异常,可以重写这个函数做错误处理。 
- (void)setValue:(id)value forUndefinedKey:(NSString *)key; 

2.4.3 语法 (获取值,获取数据)


- (id)valueForKey:(NSString *)key; 
- (id)valueForKeyPath:(NSString *)keyPath; 

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

2.4.4 其他语法


// 允许直接访问实例变量,默认返回YES。如果某个类重写了这个方法,且返回NO,则KVC不可以访问该类。 
+ (BOOL)accessInstanceVariablesDirectly; 

// 这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回 
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key; 

// 如果你在setValue方法时面给Value传nil,则会调用这个方法 
- (void)setNilValueForKey:(NSString *)key; 

// 输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。 
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys; 

// KVC 提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。 
- (BOOL)validateValue:(id)ioValue forKey:(NSString *)inKey error:(NSError)outError; 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter和Objective-C(简称OC)都是开发移动应用程序的技术,但它们在很多方面有着不同的特点。 首先,Flutter是一种跨平台的移动应用开发框架,由谷歌开发。它使用Dart语言编写,具有热重载、响应式UI框架和丰富的UI组件等特点。Flutter的一大优势是可以同时在iOS和Android平台上开发应用程序,并且拥有高性能和良好的用户体验。Flutter也支持使用原生代码进行集成,因此可以很好地与Objective-C进行交互。 Objective-C是一种面向对象的编程语言,主要用于iOS和macOS平台的应用程序开发。Objective-C采用了一种称为KVO(Key-Value Observing)的机制,允许对象对属性和值的变化进行观察和响应。通过注册观察者,当被观察对象的属性发生变化时,观察者可以接收到通知并执行相应的操作。KVO是一种非常强大的工具,可以用于实现对象之间的数据绑定和通信。 在使用Flutter开发应用时,可以与Objective-C进行集成,并利用Objective-C提供的KVO机制来实现对Flutter应用内部变量的监视和响应。这可以通过在Flutter与Objective-C之间建立桥接来实现,从而达到在Flutter应用中使用KVO的目的。 总的来说,Flutter和Objective-C KVO是两种不同的技术,Flutter是一个跨平台的移动应用开发框架,而Objective-C KVO是一种可以用于观察和响应对象属性变化的机制。在合适的场景下,可以通过Flutter与Objective-C进行集成,从而利用KVO机制来实现对Flutter应用内部变量的监视和响应。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值