进阶之路(基础篇)OC——KVC&KVO

KVC&KVO

KVC

OC中提供的一种通过字符串访问一个对象的实例变量的一种方法,采用观察者模式,在NSObject中实现。

API

- (id)valueForKey:(NSString *)key;  
- (void)setValue:(id)value forKey:(NSString *)key;  
- (id)valueForKeyPath:(NSString *)keyPath;  
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;

其中key就是要访问的属性名称所对应的字符串。keyPath是一个被点操作符隔开的用于访问指定属性的字符串序列。

使用要点

  • - (void)setValue:(id)value forKey:(NSString *)key- (void)setValue:(id)value forKeyPath:(NSString *)keyPath执行机制

1.优先考虑调用属性的setter方法。

2.如果没有setter方法,则会搜索类名中名为‘_key’的成员变量,无论其定义在接口还是实现部分,也无论其用何种访问控制符修饰,实际上是在底层实现对’_key‘成员变量赋值。

3.如果既没有setter方法,也没有’_key‘成员变量,则会搜索类名中名为’key’的成员变量,不论其定义在何处,也不论其用何种访问控制符修饰。

4.如果以上都没有找到,则调用setValue:forUndefinedKey:抛出异常。

  • - (id)valueForKey:(NSString *)key- (id)valueForKeyPath:(NSString *)keyPath执行机制

1.优先考虑调用属性的getter方法。

2.如果没有getter方法,则会搜索类名中名为‘_key’的成员变量,无论其定义在接口还是实现部分,也无论其用何种访问控制符修饰,实际上是返回’_key‘成员变量的值。

3.如果既没有getter方法,也没有’_key‘成员变量,则会搜索类名中名为’key’的成员变量,不论其定义在何处,也不论其用何种访问控制符修饰。

4.如果以上都没有找到,则调用valueForUndefinedKey抛出异常。

  • KVC支持数值和结构体属性。其自动将数值或结构体型的数据打包或解包成NSNumber或NSValue对象,以达到适配的目的。

  • KVC支持对集合运算符*。

简单集合运算符共有@avg,@count,@max,@min,@sum五种。对象运算符有两种:@distinctUnionOfObjects,@unionOfObjects。 NSArray和NSSet操作符:@distinctUnionOfArrays,@unionOfArrays,@distinctUnionOfSets。

实现原理

[object setValue:@"astring" forKey:@"name"];为例

经编译器处理成后就变成:

SEL sel = sel_get_uid ("setValue:forKey:");
IMP method = objc_msg_lookup (site->isa,sel);
method(object, sel, @"astring", @"name");

一个对象在调用setValue的时候,首先根据方法名找到运行方法的时候所需要的环境参数。他会从自己isa指针结合环境参数,找到具体的方法实现的接口。再直接查找得来的具体的方法实现。

KVO

KVO是一种典型的观察者模式,在NSObject中实现,主要用于MVC模式中的视图和模型保持同步。

API

主要API
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context ;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(nullable id)object change:(NSDictionary<NSString*, id> *)change context:( void *)context;
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;

使用要点

  • 手动通知

手动通知,需要在+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;中返回NO,并通过- (void)willChangeValueForKey:(NSString *)key- (void)didChangeValueForKey:(NSString *)key发出属性将要改变和已经改变的通知。一个完整的实现如下:

+ (BOOL)automaticallyNotifiesObserversForLComponent;
{
    return NO;
} 
/*等效于+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{if([key isEqualToStrin:@"lComponent"]){return NO;}return YES;}*/
- (void)setLComponent:(double)lComponent;
{
    if (_lComponent == lComponent) {
        return;
    }
    [self willChangeValueForKey:@"lComponent"];
    _lComponent = lComponent;
    [self didChangeValueForKey:@"lComponent"];
}
  • 通知关联

当属性A发生改变时,属性B也发出改变的通知。一个完整的实现如下:

+(NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
    NSSet *set = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"affectedKey"]) {
        NSArray *arr = [NSArray arrayWithObjects:@"affectKey", nil];
        set = [set setByAddingObjectsFromArray:arr];
    }
    return set;
}
//等效于+(NSSet *)keyPathsForValuesAffectingValueForKeyAffectedKey{......}

其中NSSet中的属性发生改变时,监听affectedKey是否改变的观察者也会收到affectedKey改变的通知。

  • 集合支持

对于NSArray和NSSet都有各自的API具体实现。具体声明在NSArray<ObjectType>(NSKeyValueObserverRegistration)NSOrderedSet<ObjectType>(NSKeyValueObserverRegistration)以及NSSet<ObjectType>(NSKeyValueObserverRegistration)

对于可变集合的动态观察,必须调用- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;,- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
才能实现被观察对象改变通知的发出。直接使用getter方法无法发出通知。
[[self mutableArrayValueForKey:@"myList"] addObject:someObject];可以向监听myList属性的观察者发出通知,而[self .myList addObject:@"world"];则不行。

  • context使用

contxt参数多用于通知的过滤,避免出现类和父类之间对属性更改通知处理的混淆。一个典型的用法如下:

static int const PrivateKVOContext;

[otherObject addObserver:self forKeyPath:@"someKey" options:someOptions context:PrivateKVOContext];
- (void)observeValueForKeyPath:(NSString *)keyPath
                  ofObject:(id)object
                    change:(NSDictionary *)change
                   context:(void *)context
{
    if (context == PrivateKVOContext) {

        // 这里写相关的观察代码
     } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}
  • 线程

KVO 行为是同步的,并且发生与所观察的值发生变化的同样的线程上。当我们试图从其他线程改变属性值的时候我们应当十分小心,除非能确定所有的观察者都用线程安全的方法处理 KVO 通知。通常来说,我们不推荐把 KVO 和多线程混起来。

  • more

监听通知时可以通过option设置存储四种参数:

NSKeyValueObservingOptionNew 改变之后的值
NSKeyValueObservingOptionOld 改变之前的旧值
NSKeyValueObservingOptionInitial 
NSKeyValueObservingOptionPrior  

对应到观察者函数里面change字典中取出进行判断做更多事情

if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) {

// 改变之前
} else {

// 改变之后
}

id oldValue = change[NSKeyValueChangeOldKey];
id newValue = change[NSKeyValueChangeNewKey];

实现原理

KVO运用了一个isa-swizzling技术。isa-swizzling就是类型混合指针机制。

当第一次观察某个Object是,runtime会创建一个继承与原class的一个subclass(NSKVONotifying_className),在subclass中重写了所有的(注意是所有的)被观察的key的set方法。然后将object的isa指针指向subclass,重写class方法,使其返回原来的class。

参考

Key-Value Observing Programming Guide
KVC/KVO原理详解及编程指南
objc系列译文:KVC 和 KVO

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值