KVC 和 KVO 的一点理解

KVC

  • 用 KVC 实现高阶消息传递

valueForKey: 有很多有用的特例,比如说 NSArray 和 NSSet 这样的容器类都覆盖了这个方法。valueForKey: 被传递给容器中得每一个对象,而不是对容器本身进行操作。结果会被添加进返回的容器中。这样,开发者能很方便的用一个容器创建另一个容器对象,比如像这样:

NSArray *array = @[@"foo",@"bar",@"baz"];
NSArray *capitals = [array valueForKey:@"capitalizedString"];

方法 capitalizedString 被传递给 NSArray 中的每一项,并返回一个包含结果的新 NSArray 。把消息(capitalizedString)作为参数传递称为高阶消息传递(Higher Order Messaging)。多个消息可以用键路径传递:

NSArray *array = @[@"foo",@"bar",@"baz"];
NSArray *capitalLengths = [array valueForKeyPath:@"capitalizedString.length"];

以上代码对 array 的每一个元素调用 capitalizedString ,然后调用 length ,在把返回值封装进 NSNumber 对象。结果被收集进名为 capitalLengths 的新数组。

  • 容器操作符

KVC 还提供了很复杂的函数,比如说自动对一组数字求和或者求平均值。看一下这个例子:

 NSArray *array = @[@"foo",@"bar",@"baz"];
 NSUInteger totalLenth = [[array valueForKeyPath:@"@sum.length"] intValue];

@sum 是一个操作符,对指定的属性(length)求和。注意,这种写法可能比等价的循环写法慢几百倍。

在处理有几千个或者几万个元素的数组时,性能问题通常会至关重要。除了 @sum ,在 IOS 开发者库的Key-Value Coding Programming
Guide
中还有很多其他的操作符。这些操作符在处理 Core Data 时尤其有用,而且比等价的循环写法快,因为它们优化为数据库查询操作。不过你不能创建自己的操作。

参考链接:Collection Operators

KVC Collection Operators

KVO

KVO是Cocoa的一个重要机制,他提供了观察某一属性变化的方法,极大的简化了代码。这种观察-被观察模型适用于这样的情况,比方说根据A(数据类)的某个属性值变化,B(view类)中的某个属性做出相应变化。对于推崇MVC的cocoa而言,KVO应用的地方非常广泛。(这样的机制听起来类似Notification,但是notification是需要一个发送notification的对象,一般是notificationCenter,来通知观察者。而KVO是直接通知到观察对象。)

使用KVO时通常遵循如下的流程:

1、注册观察

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

keyPath就是要观察的属性值,options给你观察键值变化的选择,而context方便传输你需要的数据(注意这是一个void型)

2、实现变化后的回调方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

change里存储了一些变化的数据,比如变化前的数据,变化后的数据;如果注册时context不为空,这里context就能接收到。

3、停止观察

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath

是不是觉得和NSNotificationCenter很类似,确实是有很多类似之处,都是需要注册观察和移除观察等操作,可以对比着记忆。

说明示例:

创建一个每秒自动更新1000行的表格。

KVCTableViewCell.h

@interface KVCTableViewCell : UITableViewCell
- (id)initWithReuseIdentifier:(NSString*)identifier;
@property (nonatomic, readwrite, strong) id object;
@property (nonatomic, readwrite, copy) NSString *property;
@end
@implementation KVCTableViewCell

- (BOOL)isReady {
  return (self.object && [self.property length] > 0);
}

- (void)update {
  self.textLabel.text = self.isReady ?
  [[self.object valueForKeyPath:self.property] description]
  : @"";
}

- (id)initWithReuseIdentifier:(NSString *)identifier {
  return [super initWithStyle:UITableViewCellStyleDefault
              reuseIdentifier:identifier];
}

<strong>- (void)removeObservation {
  if (self.isReady) {
    [self.object removeObserver:self
                     forKeyPath:self.property];
  }
}

- (void)addObservation {
  if (self.isReady) {
    [self.object addObserver:self forKeyPath:self.property
                     options:0
                     context:(void*)self];
  }
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
  if ((__bridge id)context == self) {
    // Our notification, not our superclass’s
      [self update];
  }
  else {
    [super observeValueForKeyPath:keyPath ofObject:object
                           change:change context:context];
  }
}

- (void)dealloc {</strong>
  if (_object && [_property length] > 0) {
    [_object removeObserver:self
                 forKeyPath:_property
                    context:(void *)self];
  }
}

- (void)setObject:(id)anObject {
  <strong>[self removeObservation];</strong>
  _object = anObject;
  <strong>[self addObservation];</strong>
  [self update];
}

- (void)setProperty:(NSString *)aProperty {
  <strong>[self removeObservation];</strong>
  _property = aProperty;
  <strong>[self addObservation];</strong>
  [self update];
}
@end

KVCTableViewController.h

@interface KVCTableViewController ()
@property (readwrite, strong) RNTimer *timer;
@property (readwrite, strong) NSDate *now;
@end

@implementation KVCTableViewController

- (void)updateNow {
  self.now = [NSDate date];
}

- (void)viewDidLoad {
  [self updateNow];

  __weak id weakSelf = self;
  self.timer =
      [RNTimer repeatingTimerWithTimeInterval:1
                                        block:^{
                                          [weakSelf updateNow];
                                        }];
}

- (void)viewDidUnload {
  self.timer = nil;
  self.now = nil;
}

- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section {
  return 100;
}

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  static NSString *CellIdentifier = @"KVCTableViewCell";

  KVCTableViewCell *cell = [tableView
             dequeueReusableCellWithIdentifier:CellIdentifier];

  if (cell == nil) {
    cell = [[KVCTableViewCell alloc]
            initWithReuseIdentifier:CellIdentifier];
    <strong>[cell setProperty:@"now"];
    [cell setObject:self];</strong>
  }

  return cell;
}

@end

在 KVCTableViewCell 中,我们根据请求用 addObservation 观察目标的属性。注册 KVO 时,要把 self 作为 context 指针传递,以便在回调中判断这是否是我们观察的事件。因为一个类只能有一个 KVO 回调,所以可能收到父类注册的属性变化事件。如果是这样,需要把回调传递给 super。不幸的是,我们不能总是传给 super,因为 NSObject 可能抛出异常。所以要用唯一的 context 来识别观察的事件。

在 KVCTableViewController 中,我们创建了一个属性 now,并且让表格单元观察此属性。每隔一秒,数据源都会更新一次。观察者就会得到通知,表格单元也会更新。这是灰常高效的,在任何时候,都只有一屏的表格单元,因为单元是可以重用的。

KVO 真正的威力表现在 [KVCTableViewController updateNow] 方法:

- (void)updateNow {
  self.now = [NSDate date];
}

唯一要做得就是更新数据。不需要操心谁在观察你,如果没有人观察你,那么就不存在任何 NSNotificationCenter 那样的开销。KVO 真正的优势就在于模型类不可思议的简洁性。只要用存取方法来修改实例变量,所有的观察机制都会自动生效,不需要付出任何成本。所有的复杂性都转移到了观察者而不是被观察者。无怪乎KVO在苹果的底层框架中越来越流行。

KVO 是如何实现的

键值观察通知依赖于NSKeyValueObserVing.h 中的两个方法:willChangeValueForKey: 和 didChangeValueForKey: 。在一个被观察值发生改变之前,willChangeValueForKey: 一定会被调用,继而 obserValueForKey: ofObject: change: context: 也会被调用。可以手动实现这些调用,但很少有人这么做。一般我们只在希望能控制回调的调用时机才会这么做。大部分情况下,改变通知会自动调用。

Object-C 中没有什么神奇的。即时是消息分发,一开始看起来很神秘,实际上也相当直观。然而,KVO确实有点魔法。调用 setNow: 时,系统还会已某种方式在中间插入 willChangeValueForKey: 、didChangValueForKe: 和 didChangeValueForKey: 和 observeValueForKeyPath: ofObject:
change: context: 的调用。大家可能以为这是因为 setNow:是合成方法,有时候我们也能看到有人这么写代码:

-(void)setNow:(NSDate *)now{
    [self willChangeValueForKey:@"now"];//没有必要
    _now = now;
    [self didChangeValueForKey:@"now"];//没有必要
}

这是完全没有必要的代码,不要这么做,这样的话,KVO代码会被调用两次。KVO在调用存取方法之前总是调用 willChangeValueForKey: ,之后总是调用 didChangValueForKey: 。怎么做到的呢?答案是通过方法混写。第一次对一个对象调用 addObserver: forKeyPath: options: context: 时,框架会创建这个类的新的KVO子类,并将被观察对象转移为新子类的的对象。在这个KVO特殊子类中,Cocoa创建观察属性的设置方法,大致工作原理如下:

-(void)setNow:(NSDate *)now{
    [self willChangeValueForKey:@"now"];
    [super setValue:now forKey:@"now"];
    [self didChangeValueForKey:@"now"];
}

这种继承和方法注入是在运行时而不是编译时实现的。这就是正确命名如此重要的原因。只有在使用KVC命名约定时,KVO才能做到这一点。

tips:

1、KVO方法混写不是很容易发现。它会覆盖class方法并返回原来的类。不过有时候我们能看到对NSKVONotifying_MYClass 而不是 MYClass 的引用。

2、建议尽量保守、简单地使用KVO,而且只在真正带来好处的地方使用。当需要大量的观察(几百个或者更多)的情况下,它的性能会比NSNotification好很多。

3、在存在复杂的相互依赖关系或者复杂的类继承层次的地方避免使用KVO。用委托和NSNotification 这种简单地解决方案,通常要比自作聪明地使用KVO解决方案要好。

一些拓展参考链接:

KVO+Block: Block Callbacks for Cocoa Observers

BlocksKit

时间: 2024-09-28 19:42:29

KVC 和 KVO 的一点理解的相关文章

KVC和KVO的一点总结

Key-value coding也即通过key来get和set对象的value,之所以这么做而不直接使用对象自带的getter和setter为了使用key-value observing功能. 那么什么是key-value observing呢?简而言之,key-value observing是一种消息通知的机制,和NSNotification的功能相似.使用NSNotification时,通过post来发消息,通过addObserver来观察消息.而key-value observing则更加

KVC 与 KVO 理解-b

KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲解. Key-Value Coding (KVC) KVC,即是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问对象的属性.KVO 就是基于 KVC 实现的关键技术之一. 一个对象拥有某些属性.比如说,一个 Person 对象有一个 name 和一个 address 属性.以 KVC 说法,Person 对象分别有一个 value 对应他的 name 和 a

对KVC和KVO的理解

对KVC和KVO的理解 kvc kvo KVC KVC是KeyValueCoding的简称,它是一种可以直接通过字符串的名字(key)来访问类属性的机制.而不是通过调用Setter.Getter方法访问. KVC实例 一个对象拥有某些属性.比如说,一个Person对象有一个name和一个address属性.以KVC为例,Person对象分别有一个value对应他的name和address的key.key只是一个字符串,它对应的值可以是任意类型的对象.从最基础的层次上看,KVC有两个方法:一个是设

[转] iOS (OC) 中 KVC 与 KVO 理解

转自: http://magicalboy.com/kvc_and_kvo/ KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲解. Key-Value Coding (KVC) KVC,即是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问对象的属性.KVO 就是基于 KVC 实现的关键技术之一. 一个对象拥有某些属性.比如说,一个 Person 对象有一个 name 和一个 address 属性.以 KV

iOS开发-KVC和KVO的理解

KVC和KVO看起来很专业,其实用起来还是比较简单的,KVC(Key-value coding)可以理解为键值对编码,如果对象的基本类型,那么键值对编码实际上和get,set方法没有区别,如果是属性是另外一个对象,那么发现KVC用起来还是非常顺手,KVO(key-value observing)是键值对的观察者模式,如果对象的属性发生变更,那么会触发observeValueForKeyPath事件,KVO的这种通知特性让我们在开发的时候节省了不必要的代码,提高了开发效率. KVC键值对编码 KV

KVC 与 KVO 理解

KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲解. Key-Value Coding (KVC) KVC,即是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问对象的属性.KVO 就是基于 KVC 实现的关键技术之一. 一个对象拥有某些属性.比如说,一个 Person 对象有一个 name 和一个 address 属性.以 KVC 说法,Person 对象分别有一个 value 对应他的 name 和 a

我所认为的KVC和KVO

引子:    ?   为什么要写这个,只是突然一个念头闪现,说一下本人目前理解.KVC:    ?   Key-value coding,它是一种使用字符串标识符,间接访问对象属性的机制.但是关就这一点来说,这么久以来确实没怎么体会到用这个的好处,我没有明显的理由用setValut:forKey 而不是直接用一个属性.当然,KVC是KVO实现的基础,这个基础究竟是什么?下来道来KVO:       ?KVO其实就是一种观察者模式的实现.两个角色:被观察者A和观察者B.假如B观察A的某一个属性,那

KVC 与 KVO

IOS开发系列--Objective-C之KVC.KVO 2014-07-27 11:29 by KenshinCui, 18479 阅读, 9 评论, 收藏, 编辑 概述 由于ObjC主要基于Smalltalk进行设计,因此它有很多类似于Ruby.Python的动态特性,例如动态类型.动态加载.动态绑定等.今天我们着重介绍ObjC中的键值编码(KVC).键值监听(KVO)特性: 键值编码KVC 键值监听KVO 键值编码KVC 我们知道在C#中可以通过反射读写一个对象的属性,有时候这种方式特别方

KVC and KVO

KVC 与 KVO 理解 On 2012 年 6 月 7 日, in iPhone, by donly KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲解. Key-Value Coding (KVC) KVC,即是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问对象的属性.KVO 就是基于 KVC 实现的关键技术之一. 一个对象拥有某些属性.比如说,一个 Person 对象有一个 name 和一个 ad