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
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型)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)contextchange里存储了一些变化的数据,比如变化前的数据,变化后的数据;如果注册时context不为空,这里context就能接收到。
3、停止观察
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
说明示例:
创建一个每秒自动更新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
@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
在 KVCTableViewController 中,我们创建了一个属性 now,并且让表格单元观察此属性。每隔一秒,数据源都会更新一次。观察者就会得到通知,表格单元也会更新。这是灰常高效的,在任何时候,都只有一屏的表格单元,因为单元是可以重用的。
KVO 真正的威力表现在 [KVCTableViewController updateNow] 方法:
- (void)updateNow { self.now = [NSDate date]; }
键值观察通知依赖于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"];//没有必要 }
-(void)setNow:(NSDate *)now{ [self willChangeValueForKey:@"now"]; [super setValue:now forKey:@"now"]; [self didChangeValueForKey:@"now"]; }
tips:
1、KVO方法混写不是很容易发现。它会覆盖class方法并返回原来的类。不过有时候我们能看到对NSKVONotifying_MYClass 而不是 MYClass 的引用。
2、建议尽量保守、简单地使用KVO,而且只在真正带来好处的地方使用。当需要大量的观察(几百个或者更多)的情况下,它的性能会比NSNotification好很多。
3、在存在复杂的相互依赖关系或者复杂的类继承层次的地方避免使用KVO。用委托和NSNotification 这种简单地解决方案,通常要比自作聪明地使用KVO解决方案要好。
一些拓展参考链接:
KVO+Block: Block Callbacks for Cocoa Observers
原文地址:http://blog.csdn.net/chaoyuan899/article/details/44699503