KVO是Key-Value-Observer的缩写,使用的是观察者模式。底层实现机制都是isa-swizzing,就是在底层调用object_setClass
函数,将对象的isa指向的Class偷偷换掉。php
而观察者模式就是 目标对象(被观察的对象)管理全部依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象。而主动通知观察者对象这个实现通常都是调用观察者对象提供的接口。这样就能够将目标对象和观察者对象松散偶合。安全
iOS 中的实现就更简单了,利用respondsToSelector
来判断观察者是否实现了指定的方法,就能够通知观察者对象了。bash
KVO的实现依赖于runtime,它须要动态获取到class,也须要动态的修改class,还须要动态判断是否实现了某些方法等。框架
原理:当第一次观察某个类的实例对象时,会动态建立一个该类的子类,而后将该对象的isa修改成这个新的子类的Class,重写被观察的属性的 set方法,而后在修改属性先后,调用观察者的接口来通知观察者。ide
GNUstep是Objective-C中大部分实现的前身,虽然OC在GNUstep的基础上作了许多更新和优化,可是不少基本逻辑思路是一致的。而KVO的源码又没有开源,因此咱们就只能先从GNUstep的实现中来参考一二了。函数
[GNUstep Core Base](wwwmain.gnustep.org/resources/d… 中有Foundation框架的实现。虽然可能与OC的实现不太同样,可是整体思路是同样的。优化
咱们在下载的开源工程中的【base/Headers/Foundation/NSKeyValueObserving.h】中能够看到KVO相关的头文件。ui
这个NSKeyValueObserving.h
中暴露的API与Objective-C中Foudation中NSKeyValueObserving.h
中的API基本上是一致的。atom
都是为NSObjet增长了几个Category,分别放了KVO要实现的键值观察方法和添加观察者、移除观察者等API方法。spa
咱们能够在【base/Source/Foundation/KVO】目录下找到NSKeyValueObserving.m
。
先来看一下源码,因为是GNUstep的开源框架,因此部分类型仍是GS前缀,为了便于理解,我已添加一些注释。
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
GSKVOInfo *info;
GSKVOReplacement *r;
NSKeyValueObservationForwarder *forwarder;
NSRange dot;
// 1.初始化一些全局变量
setup();
// 2.使用递归锁保证线程安全
[kvoLock lock];
// 3.从全局NSMapTable中获取某个类的KVO子类Class
r = replacementForClass([self class]);
// 4.从全局NSMapTable中获取某个类的观察者信息对象
info = (GSKVOInfo*)[self observationInfo];
// 5.若是不存在就建立一个观察者信息对象实例。
if (info == nil)
{
info = [[GSKVOInfo alloc] initWithInstance: self];
// 5.1 保存到全局NSMapTable中。
[self setObservationInfo: info];
// 5.2 将被观察的对象的isa修改成新的KVO子类Class
object_setClass(self, [r replacement]);
}
// 6.调用info实例方法处理观察
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];
}
// 7.递归锁解锁
[kvoLock unlock];
}
复制代码
setup()
setup()
函数中主要是对一些全局变量的初始化,固然了内部也加了递归锁,以及全局变量是否为空的判断。
// 全局递归锁
static NSRecursiveLock *kvoLock = nil;
static NSMapTable *classTable = 0;
static NSMapTable *infoTable = 0;
static NSMapTable *dependentKeyTable;
static Class baseClass;
static id null;
// 这个是在GSLock中定义的
NSRecursiveLock *gnustep_global_lock = nil;
static inline void
setup()
{
if (nil == kvoLock)
{
[gnustep_global_lock lock];
if (nil == kvoLock)
{
kvoLock = [NSRecursiveLock new];
null = [[NSNull null] retain];
classTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
NSNonOwnedPointerMapValueCallBacks, 128);
infoTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
NSNonOwnedPointerMapValueCallBacks, 1024);
dependentKeyTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
NSOwnedPointerMapValueCallBacks, 128);
baseClass = NSClassFromString(@"GSKVOBase");
}
[gnustep_global_lock unlock];
}
}
复制代码
以上源代码基本上都在NSKeyValueObserving.m
的顶部。
NSMapTable 是iOS 是iOS 6 新增的容器类,功能相似于NSDictionary。通常的字典,会持有key 和value,致使对象的引用计数增长。可是NSMapTable能够分别设置key 和value的持有状况,若是对key 和 value是弱引用,当key 和 value被释放销毁后,NSMapTable中对应的数据也会被清除。
replacementForClass()
这是一个全局静态函数,做用是从全局classTable中获取已经建立的某个类的KVO子类。
static GSKVOReplacement *
replacementForClass(Class c)
{
GSKVOReplacement *r;
// 0.方式全局变量没有初始化
setup();
// 1.使用递归锁锁住
[kvoLock lock];
// 2.从全局classTable中获取GSKVOReplacement实例
r = (GSKVOReplacement*)NSMapGet(classTable, (void*)c);
// 3.若是不存在,就建立一个保存到全局classTable中
if (r == nil)
{
r = [[GSKVOReplacement alloc] initWithClass: c];
NSMapInsert(classTable, (void*)c, (void*)r);
}
// 4.释放递归锁
[kvoLock unlock];
return r;
}
复制代码
而GSKVOReplacement
中其实主要存储的是原始的Class以及对象被更新后的Class和被观察的keys。
@interface GSKVOReplacement : NSObject
{
Class original; /* The original class */
Class replacement; /* The replacement class */
NSMutableSet *keys; /* The observed setter keys */
}
- (id) initWithClass: (Class)aClass;
- (void) overrideSetterFor: (NSString*)aKey;
- (Class) replacement;
@end
复制代码
GSKVOInfo
全局infoTable中存储的就是该类型的实例对象。
@interface GSKVOInfo : NSObject
{
NSObject *instance; // Not retained.
NSRecursiveLock *iLock;
NSMapTable *paths;
}
- (GSKVOPathInfo *) lockReturningPathInfoForKey: (NSString *)key;
- (void*) contextForObserver: (NSObject*)anObserver ofKeyPath: (NSString*)aPath;
- (id) initWithInstance: (NSObject*)i;
- (NSObject*) instance;
- (BOOL) isUnobserved;
- (void) unlock;
@end
复制代码
-observationInfo和 -setObservationInfo:
这两个函数主要是从全局infoTable中存取对象而已,比较简单就不作赘述了。
- (void*) observationInfo
{
void *info;
setup();
[kvoLock lock];
info = NSMapGet(infoTable, (void*)self);
IF_NO_GC(AUTORELEASE(RETAIN((id)info));)
[kvoLock unlock];
return info;
}
- (void) setObservationInfo: (void*)observationInfo
{
setup();
[kvoLock lock];
if (observationInfo == 0)
{
NSMapRemove(infoTable, (void*)self);
}
else
{
NSMapInsert(infoTable, (void*)self, observationInfo);
}
[kvoLock unlock];
}
复制代码
object_setClass(self, [r replacement])
这里的[r replacement]
其实仅仅是获取到GSKVOReplacement内的replacement成员变量的值而已。而生成replacement的过程在init函数中。
- (id) initWithClass: (Class)aClass
{
original = aClass;
/*
* Create subclass of the original, and override some methods
* with implementations from our abstract base class.
*/
NSString *superName = NSStringFromClass(original);
NSString *name = [@"GSKVO" stringByAppendingString: superName];
// 这里利用runtime动态建立一个集成自original的GSKVOxxx类
NSValue *template = GSObjCMakeClass(name, superName, nil);
// 将新的GSKVOXXX类,注册到系统中
GSObjCAddClasses([NSArray arrayWithObject: template]);
replacement = NSClassFromString(name);
// 将baseClass(GSKVOBase)中的API添加到replacement上。
GSObjCAddClassBehavior(replacement, baseClass);
keys = [NSMutableSet new];
return self;
}
复制代码
这里比较重要的实际上是GSObjCAddClassBehavior(replacement, baseClass)
,由于GSKVOBase
中一共也没几个API,主要是实现了以下几个API:
- (void) dealloc
{
// Turn off KVO for self ... then call the real dealloc implementation.
[self setObservationInfo: nil];
object_setClass(self, [self class]);
[self dealloc];
GSNOSUPERDEALLOC;
}
- (Class) class
{
return class_getSuperclass(object_getClass(self));
}
- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
Class c = [self class];
void (*imp)(id,SEL,id,id);
imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
imp(self,_cmd,anObject,aKey);
[self didChangeValueForKey: aKey];
}
else
{
imp(self,_cmd,anObject,aKey);
}
}
- (void) takeStoredValue: (id)anObject forKey: (NSString*)aKey
{
Class c = [self class];
void (*imp)(id,SEL,id,id);
imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
imp(self,_cmd,anObject,aKey);
[self didChangeValueForKey: aKey];
}
else
{
imp(self,_cmd,anObject,aKey);
}
}
- (void) takeValue: (id)anObject forKey: (NSString*)aKey
{
Class c = [self class];
void (*imp)(id,SEL,id,id);
imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
imp(self,_cmd,anObject,aKey);
[self didChangeValueForKey: aKey];
}
else
{
imp(self,_cmd,anObject,aKey);
}
}
- (void) takeValue: (id)anObject forKeyPath: (NSString*)aKey
{
Class c = [self class];
void (*imp)(id,SEL,id,id);
imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
imp(self,_cmd,anObject,aKey);
[self didChangeValueForKey: aKey];
}
else
{
imp(self,_cmd,anObject,aKey);
}
}
- (Class) superclass
{
return class_getSuperclass(class_getSuperclass(object_getClass(self)));
}
复制代码
这几个函数的实现都很简单,主要做用就是为了让开发者感知不到GSKVOxxx类的存在,由于当开发者在使用这些函数时,取到的仍是original类的信息。
接下来,分两种状况:
NSKeyValueObservationForwarder
对象,再调用GSKVOInfo
中的- addObserver: forKeyPath: options: context:
函数。状况1
GSKVOReplacement
中的overrideSetterFor
实现,也就是拼接出setXxx:
或者_setXxx:
,而后获取到SEL,最后将GSKVOSetter
中针对各类类型的setter imp 赋值给sel。
关于各类类型的属性的set方法的实现,已经集中在GSKVOSetter
中实现了。另外,赋值使用的是以下API:class_addMethod(replacement, sel, imp, [sig methodType])
。
最后,调用GSKVOInfo
中的- addObserver: forKeyPath: options: context:
函数。调用该API目的有两个:
GSKVOInfo
中的paths中,方便之后直接从内存中取。状况2
这种状况的实现,其实都在以下函数中:
- (id) initWithKeyPath: (NSString *)keyPath
ofObject: (id)object
withTarget: (id)aTarget
context: (void *)context
{
NSString * remainingKeyPath;
NSRange dot;
target = aTarget;
keyPathToForward = [keyPath copy];
contextToForward = context;
dot = [keyPath rangeOfString: @"."];
if (dot.location == NSNotFound)
{
[NSException raise: NSInvalidArgumentException
format: @"NSKeyValueObservationForwarder was not given a key path"];
}
keyForUpdate = [[keyPath substringToIndex: dot.location] copy];
remainingKeyPath = [keyPath substringFromIndex: dot.location + 1];
observedObjectForUpdate = object;
[object addObserver: self
forKeyPath: keyForUpdate
options: NSKeyValueObservingOptionNew
| NSKeyValueObservingOptionOld
context: target];
dot = [remainingKeyPath rangeOfString: @"."];
if (dot.location != NSNotFound)
{
child = [[NSKeyValueObservationForwarder alloc]
initWithKeyPath: remainingKeyPath
ofObject: [object valueForKey: keyForUpdate]
withTarget: self
context: NULL];
observedObjectForForwarding = nil;
}
else
{
keyForForwarding = [remainingKeyPath copy];
observedObjectForForwarding = [object valueForKey: keyForUpdate];
[observedObjectForForwarding addObserver: self
forKeyPath: keyForForwarding
options: NSKeyValueObservingOptionNew
| NSKeyValueObservingOptionOld
context: target];
child = nil;
}
return self;
}
复制代码
举个例子,若是咱们要观察的Parent中的成员变量child的height属性,keyPath实际上是child.height
。 那上面该方法作的事情,实际上是先建立一个KVO监听的是其属性child的变动,而后再执行child的KVO,监听child
对象的成员变量height的变动。
这里child的观察者是NSKeyValueObservationForwarder
对象,而后在内部的- observeValueForKeyPath:ofObject:change:context:
中调用上一级的对象的- observeValueForKeyPath:ofObject:change:context:
,这样就能够将要监听的属性的变动事件一级一级的传出去。
我这里建立了一个HLPerson
类:
@interface HLPerson : NSObject
@property (nonatomic, assign) int height;
@end
复制代码
而后在viewController的viewDidLoad中初始化该person:
self.person = [[HLPerson alloc] init];
NSLog(@"Class:%@", object_getClass(self.person));
NSLog(@"person.class:%@", self.person.class);
[self.person addObserver:self forKeyPath:@"height" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"Class:%@", object_getClass(self.person));
NSLog(@"person.class:%@", self.person.class);
// 输出结果为:
Class:HLPerson
person.class:HLPerson
Class:NSKVONotifying_HLPerson
person.class:HLPerson
复制代码
因而可知,苹果中的实现是构造出了一个NSKVONotifying_HLPerson
,虽然跟GNUstep中的前缀不太同样,可是实现逻辑应该是差很少的。
虽然结论是猜想的,可是可信度应该是很是高的。KVO的实现原理,也就是对象执行- addObserver: forKeyPath: options: context:
时,内部实现以下:
setValue:forKey
等函数。在真正执行赋值操做先后插入willChange 和 didChange 方法。- observeValueForKeyPath:ofObject:change:context:
。- observeValueForKeyPath:ofObject:change:context:
告知观察对象了。