收录:原文地址
KVO
在我们实际开发之中运用非常之多,很多开发者都知道原理!但是这些原理是如何来的,一般都是浅尝辄止。这个篇章我会从Swift
入手分析,探索KVO
底层源码.希望让读者真正掌握这一块底层,知其然而知其所以然!
KVO简介
首先我们从KVO
的三部曲开始
// 1: 添加观察
person.addObserver(self, forKeyPath: "name", options: .new, context: nil)
// 2: 观察响应回调
override func observeValue(forKeyPath keyPath:, of object:, change: , context:){}
// 3: 移除观察
person.removeObserver(self, forKeyPath: "name")
其实我们也知道,就是平时在开发的时候,我们也可以通过计算型属性也可以直接观察
var name: String = ""{
willSet{
print(newValue)
}
didSet{
print(oldValue)
}
}
问题来了:这两者有什么关系?
KVO与计算型属性的关系
下面我们开始分析,首先感谢苹果开源精神,在Github可以直接下载,我们通过 Swift
源码展开分析
public func willChangeValue(for keyPath: __owned KeyPath) {
(self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath))
}
public func willChange(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: __owned KeyPath) {
(self as! NSObject).willChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
}
public func willChangeValue(for keyPath: __owned KeyPath, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set) -> Void {
(self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
}
public func didChangeValue(for keyPath: __owned KeyPath) {
(self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath))
}
public func didChange(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: __owned KeyPath) {
(self as! NSObject).didChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
}
public func didChangeValue(for keyPath: __owned KeyPath, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set) -> Void {
(self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
}
-
willChangeValue
和didChangeValue
作为数据改变的两个重要的方法 - 我们通过这两个方法继续展开分析
class Target : NSObject, NSKeyValueObservingCustomization {
// This dynamic property is observed by KVO
@objc dynamic var objcValue: String
@objc dynamic var objcValue2: String {
willSet {
willChangeValue(for: \.objcValue2)
}
didSet {
didChangeValue(for: \.objcValue2)
}
}
}
- 很明显的继承关系来自
NSObject
- 实现了
NSKeyValueObservingCustomization
的协议
public protocol NSKeyValueObservingCustomization : NSObjectProtocol {
static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set
static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool
}
- 这也是我们两个非常重要的方法,平时在开发也是很有利的方法,
keyPathsAffectingValue
能够建立keyPath
的依赖,例如两个属性的变化同时影响一个重要属性的改变:进度 = 下载量 / 总量
-
automaticallyNotifiesObservers
自动开关 - 很明显我们的计算型属性在
willSet
里面就调用willChangeValue
,didSet
调用didChangeValue
,的确我们计算型属性是和我们KVO相关方法是有所关联,这里也直接证明! - OK,我们探索完这个问题,我们摸着这条线继续探索
KVO底层
!
KVO底层
这里说明一下,本篇章的贴出的源码没有给大家省略,目的是想让大家认真阅读,自己对照学习。当然可能中间我也忽略过一些细节,源码直接贴出来方便自己理解
添加观察
person.addObserver(self, forKeyPath: "name", options: .new, context: nil)
现在我们开始探索底层源码:
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
GSKVOInfo *info;
GSKVOReplacement *r;
NSKeyValueObservationForwarder *forwarder;
NSRange dot;
setup();
[kvoLock lock];
// Use the original class
r = replacementForClass([self class]);
/*
* Get the existing observation information, creating it (and changing
* the receiver to start key-value-observing by switching its class)
* if necessary.
*/
info = (GSKVOInfo*)[self observationInfo];
if (info == nil)
{
info = [[GSKVOInfo alloc] initWithInstance: self];
[self setObservationInfo: info];
object_setClass(self, [r replacement]);
}
/*
* Now add the observer.
*/
dot = [aPath rangeOfString:@"."];
if (dot.location != NSNotFound)
{
forwarder = [[NSKeyValueObservationForwarder alloc]
initWithKeyPath: aPath
ofObject: self
withTarget: anObserver
context: aContext];
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: forwarder];
}
else
{
[r overrideSetterFor: aPath];
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: aContext];
}
[kvoLock unlock];
}
-
中间
replacementForClass
做了一些处理:- 创建了一个动态子类名字:"NSKVONotifing_原类的名字"
- 添加了
class、set、dealloc
方法 - 原类的
isa
与动态isa
切换
由原来的观察者进行迁移到
GSKVOInfo
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
GSKVOPathInfo *pathInfo;
GSKVOObservation *observation;
unsigned count;
if ([anObserver respondsToSelector:
@selector(observeValueForKeyPath:ofObject:change:context:)] == NO)
{
return;
}
[iLock lock];
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
if (pathInfo == nil)
{
pathInfo = [GSKVOPathInfo new];
// use immutable object for map key
aPath = [aPath copy];
NSMapInsert(paths, (void*)aPath, (void*)pathInfo);
[pathInfo release];
[aPath release];
}
observation = nil;
pathInfo->allOptions = 0;
count = [pathInfo->observations count];
while (count-- > 0)
{
GSKVOObservation *o;
o = [pathInfo->observations objectAtIndex: count];
if (o->observer == anObserver)
{
o->context = aContext;
o->options = options;
observation = o;
}
pathInfo->allOptions |= o->options;
}
if (observation == nil)
{
observation = [GSKVOObservation new];
GSAssignZeroingWeakPointer((void**)&observation->observer,
(void*)anObserver);
observation->context = aContext;
observation->options = options;
[pathInfo->observations addObject: observation];
[observation release];
pathInfo->allOptions |= options;
}
if (options & NSKeyValueObservingOptionInitial)
{
/* If the NSKeyValueObservingOptionInitial option is set,
* we must send an immediate notification containing the
* existing value in the NSKeyValueChangeNewKey
*/
[pathInfo->change setObject: [NSNumber numberWithInt: 1]
forKey: NSKeyValueChangeKindKey];
if (options & NSKeyValueObservingOptionNew)
{
id value;
value = [instance valueForKeyPath: aPath];
if (value == nil)
{
value = null;
}
[pathInfo->change setObject: value
forKey: NSKeyValueChangeNewKey];
}
[anObserver observeValueForKeyPath: aPath
ofObject: instance
change: pathInfo->change
context: aContext];
}
[iLock unlock];
}
- 判断我们的观察者是否能够响应:
observeValueForKeyPath:ofObject:change:context:
方法。常规操作,没有回调,响应就没有什么意义了! - 通过获取
pathInfo
来保存KVO信息
- 中间对
context
&options
的处理数据 -
NSKeyValueObservingOptionInitial
就会主动发起一次KVO
响应:observeValueForKeyPath
观察属性变化的时候
- (void) willChangeValueForKey: (NSString*)aKey
{
GSKVOPathInfo *pathInfo;
GSKVOInfo *info;
info = (GSKVOInfo *)[self observationInfo];
if (info == nil)
{
return;
}
pathInfo = [info lockReturningPathInfoForKey: aKey];
if (pathInfo != nil)
{
if (pathInfo->recursion++ == 0)
{
id old = [pathInfo->change objectForKey: NSKeyValueChangeNewKey];
if (old != nil)
{
/* We have set a value for this key already, so the value
* we set must now be the old value and we don't need to
* refetch it.
*/
[pathInfo->change setObject: old
forKey: NSKeyValueChangeOldKey];
[pathInfo->change removeObjectForKey: NSKeyValueChangeNewKey];
}
else if (pathInfo->allOptions & NSKeyValueObservingOptionOld)
{
/* We don't have an old value set, so we must fetch the
* existing value because at least one observation wants it.
*/
old = [self valueForKey: aKey];
if (old == nil)
{
old = null;
}
[pathInfo->change setObject: old
forKey: NSKeyValueChangeOldKey];
}
[pathInfo->change setValue:
[NSNumber numberWithInt: NSKeyValueChangeSetting]
forKey: NSKeyValueChangeKindKey];
[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES];
}
[info unlock];
}
[self willChangeValueForDependentsOfKey: aKey];
}
- 通过
pathInfo
获取回之前的旧值 -
pathInfo->change
里面的数据处理 - 重点:
[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES];
开始发起响应通知
- (void) notifyForKey: (NSString *)aKey ofInstance: (id)instance prior: (BOOL)f
{
unsigned count;
id oldValue;
id newValue;
if (f == YES)
{
if ((allOptions & NSKeyValueObservingOptionPrior) == 0)
{
return; // Nothing to do.
}
[change setObject: [NSNumber numberWithBool: YES]
forKey: NSKeyValueChangeNotificationIsPriorKey];
}
else
{
[change removeObjectForKey: NSKeyValueChangeNotificationIsPriorKey];
}
oldValue = [[change objectForKey: NSKeyValueChangeOldKey] retain];
if (oldValue == nil)
{
oldValue = null;
}
newValue = [[change objectForKey: NSKeyValueChangeNewKey] retain];
if (newValue == nil)
{
newValue = null;
}
/* Retain self so that we won't be deallocated during the
* notification process.
*/
[self retain];
count = [observations count];
while (count-- > 0)
{
GSKVOObservation *o = [observations objectAtIndex: count];
if (f == YES)
{
if ((o->options & NSKeyValueObservingOptionPrior) == 0)
{
continue;
}
}
else
{
if (o->options & NSKeyValueObservingOptionNew)
{
[change setObject: newValue
forKey: NSKeyValueChangeNewKey];
}
}
if (o->options & NSKeyValueObservingOptionOld)
{
[change setObject: oldValue
forKey: NSKeyValueChangeOldKey];
}
[o->observer observeValueForKeyPath: aKey
ofObject: instance
change: change
context: o->context];
}
[change setObject: oldValue forKey: NSKeyValueChangeOldKey];
[oldValue release];
[change setObject: newValue forKey: NSKeyValueChangeNewKey];
[newValue release];
[self release];
}
- change里面值的处理完毕之后
- 让我们的观察者响应实现的KVO回调方法:
[o->observer observeValueForKeyPath: aKey ofObject: instance change: change context: o->context];
- 完美看到响应回调,舒服
移除观察者
移除观察的流程相对来说,比较简单了,但是优秀的我还是愿意和大家一起探索
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
GSKVOInfo *info;
id forwarder;
/*
* Get the observation information and remove this observation.
*/
info = (GSKVOInfo*)[self observationInfo];
forwarder = [info contextForObserver: anObserver ofKeyPath: aPath];
[info removeObserver: anObserver forKeyPath: aPath];
if ([info isUnobserved] == YES)
{
/*
* The instance is no longer being observed ... so we can
* turn off key-value-observing for it.
*/
object_setClass(self, [self class]);
IF_NO_GC(AUTORELEASE(info);)
[self setObservationInfo: nil];
}
if ([aPath rangeOfString:@"."].location != NSNotFound)
[forwarder finalize];
}
- 拿回我们
observationInfo
就是我们信息收集者 - 利用
NSMapRemove(paths, (void*)aPath)
移除 - 动态子类的
isa
和原类的isa
切换回来 - 把当前设置的
info
置空
OK 完美解析了KVO底层源码!我们在探索完KVO底层实现才能说是真正的掌握了,而不是通过面试宝典背下结论,那是没有什么意义! 在真正的高手对决间一眼就能看出,中间忽略了一些小细节,比如set的多种情况,
setNumber
类型,setInt
类型,setLong
类型....我相信聪明的你一样可以解析读懂!
对于以上技术点,想要更好的探讨,可以进入iOS技术圈,一起探讨学习
- 点击加入与 2000+iOS开发者一起探讨交流