RxSwift-KVO底层探索(上)
2012 年 10 月 8 日
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) -> Setstatic 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]; } 复制代码
[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
类型….我相信聪明的你一样可以解析读懂! 就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!