KVC的一些使用技巧,能够参考以前的一个简单记录: iOS中关于KVC使用的一些小技巧数组
KVO是基于KVC基础的键值观察机制。 KVO的基本使用就不说了,来看看添加KVO对Person的影响:bash
本文中的代码都基于Person类的定义:框架
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
复制代码
static void *kContextPersonKVO = &kContextPersonKVO;
...
Person *p = [[Person alloc] init];
[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:kContextPersonKVO];
p.age = 10; // 断点
复制代码
在断点处查看:异步
KVO的实现机制是isa-swizzling。async
Automatic key-value observing is implemented using a technique called isa-swizzling. The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data. When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.post
KVO作了isa-swizzling以后,会动态建立一个继承自原来Class的子类。如原类为Person,则动态建立一个NSKVONotifying_Person继承自Person类。 对Person类的一个实例对象p而言,对其某个属性添加KVO以后, 使用object_getClass(self)方法来查看其isa为NSKVONotifying_Person, 使用class_getSuperclass(object_getClass(self))来查看其super class为Person。 因此, isa-swizzling的关键点在于将被观察对象(实例对象p)的isa指针指向新建立的类NSKVONotifying_Person。 而使用[self class]获得的依然是Person,[self superClass]获得的是NSObject。 因此,对于咱们来说,能够理解为:添加KVO以后,被观察对象(实例对象p)的isa,super_class,检测属性的setter方法被改变了。好比,咱们调用属性age的setter方法,实际上会去NSKVONotifying_Person中找到对应重写的setAge:方法,使用willChangeForValue:和didChangeForValue:来实现setter方法的监听。 若是不经过setter方法,而是直接给实例变量_age赋值,是不会触发KVO的响应方法的。优化
removeObserver方法将isa再指向原来的Person类便可。ui
这一点是KVO最基本的用法,就很少说了atom
KVO的另外一个最多见使用场景就是观察者模式。 如对Person的实例变量p的age属性进行KVO监控,能够随时获取age的变化,作出对应的响应。spa
使用KVO能够实现双向绑定,用于封装一个响应式的框架。 这一点,RAC和RxSwift值得研究一番。
若是使用KVO监听了UIView的frame属性,改变其center属性,是不会触发KVO的。由于改变center并未调用frame属性的setter方法,能够在center的setter方法中使用willChangeValueFor:和didChangeValueFor:来触发frame属性的KVO。
- (void)setCenter:(CGPoint)center
{
[aView willChangeValueForKey:@"center"];
// 根据center计算new frame
CGRect newFrame = xxx;
aView.frame = newFrame;
[aView didChangeValueForKey:@"center"];
}
复制代码
默认状况下,NSOperation的这三个属性是只读的,
@interface NSOperation : NSObject
@property (readonly, getter=isCancelled) BOOL cancelled;
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@end
复制代码
那咱们若是想给这三个属性赋值,已达到本身控制NSOperation状态的目的呢? 可使用以下方式:
@interface CSDownloadOperation : NSOperation
@end
@interface CSDownloadOperation ()
// 因这些属性是readonly, 不会自动触发KVO. 须要手动触发KVO, 见setter方法.
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@end
@implementation CSDownloadOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
@synthesize cancelled = _cancelled;
// MARK: - setter
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
/**
finished
设置isFinished状态, 不能在start以前执行, 不然会crash.
*/
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setCancelled:(BOOL)cancelled
{
[self willChangeValueForKey:@"isCancelled"];
_cancelled = cancelled;
[self didChangeValueForKey:@"isCancelled"];
}
@end
复制代码
在须要设置新值的时候,手动触发KVO,而后给对应的实例变量赋值。 这种方式能够用来自定义一个异步执行的NSOperation,好比使用NSURLSession封装的下载操做。
使用KVO的常见方法不能对可变集合进行监控,只能经过mutableArrayValueForKey:, mutableSetValueForKey:, mutableOrderedSetValueForKey:来分别对NSMutableArray,NSMutableSet,NSMutableOrderedSet进行监控。
We would also like to point out that collections as such are not observable. KVO is about observing relationships rather than collections. We cannot observe an NSArray; we can only observe a property on an object – and that property may be an NSArray. As an example, if we have a ContactList object, we can observe its contacts property, but we cannot pass an NSArray to -addObserver:forKeyPath:... as the object to be observed.
好比以下代码,咱们想要对一个可变数组selectedMaterials进行KVO监控,以便对UI和代码逻辑进行更新。
@property (nonatomic, strong) NSMutableArray *selectedMaterials;
[self addObserver:self
forKeyPath:@"selectedMaterials"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
context:&ctxKVOSelectedMaterials];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == &ctxKVOSelectedMaterials) {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateSelectedCount];
[self.collectionView reloadData];
});
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
[self removeObserver:self
forKeyPath:@"selectedMaterials"
context:&ctxKVOSelectedMaterials];
复制代码
同时,触发KVO的代码也有所不一样。必定要先获取到可变集合[self mutableArrayValueForKey:@"selectedMaterials"]
// 每次add都会触发一次KVO
[[self mutableArrayValueForKey:@"selectedMaterials"] addObject:material.number];
[[self mutableArrayValueForKey:@"selectedMaterials"] removeObject:material.number];
[[self mutableArrayValueForKey:@"selectedMaterials"] removeAllObjects];
复制代码
// 屡次add仅触发一次KVO
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.selectedMaterials.count, materials.count)];
[[self mutableArrayValueForKey:@"selectedMaterials"] insertObjects:materials atIndexes:indexSet];
复制代码
参考文档:
In order to be key-value coding compliant for a mutable ordered to-many relationship you must implement the following methods: -insertObject:inAtIndex: or -insert:atIndexes:. At least one of these methods must be implemented. These are analogous to the NSMutableArray methods insertObject:atIndex: and insertObjects:atIndexes:. -removeObjectFromAtIndex: or -removeAtIndexes:. At least one of these methods must be implemented. These methods correspond to the NSMutableArray methods removeObjectAtIndex: and removeObjectsAtIndexes: respectively. -replaceObjectInAtIndex:withObject: or -replaceAtIndexes:with:. Optional. Implement if benchmarking indicates that performance is an issue. The -insertObject:inAtIndex: method is passed the object to insert, and an NSUInteger that specifies the index where it should be inserted. The -insert:atIndexes: method inserts an array of objects into the collection at the indices specified by the passed NSIndexSet. You are only required to implement one of these two methods.
针对这一点,有一些第三方库本身对KVO进行了封装,添加了可传递block的API,相似于NSNotificationCenter的某些方法。
这一点尤为要注意。通常确保addObserver与removeObserver成对出现便可。
KVO是runtime的一个特性,因此在Swift中KVO仅对NSObject的子类有效,且须要对监听的属性使用dynamic关键字。不过,Swift中的属性有了willSet和didSet方法,相比KVO更加实用。
KVO的observeValueForKeyPath方法执行的线程,始终与执行被监听属性的setter方法的代码处于同一线程。若要在observeValueForKeyPath执行其余线程的任务,可使用dispatch_async(xxx). 这一点与NSNotification相似。NSNotification不能跨线程:即响应通知的action,默认是与postNotification在同一个线程的,若想在指定线程中执行响应通知的方法,可使用带有block的addObserver方法,或者使用dispatch_async(xxx)。
FBKVOController是Facebook开源的KVO封装库,针对KVO的一些缺点作了优化,使用也更加简便。