在日常开发中经常会用到KVO,而RxSwift框架也有KVO,在了解RxSwift框架的KVO之前,我们先来了解一下系统KVO的底层原理。
(在这之前说个题外话,以前面试的时候曾经被问到:“KVO的底层原理是什么?”,当时我说:“不知道,我猜可能在setter方法的底层源码里加了个回调。”,当时觉得自己好菜♂️,虽然说了总比不说好,可是说了个好傻的答案。)
- 但是大家都知道,苹果的源码看不了,那怎么办呢?还有一个靠谱的方法就是看GNUstep,因为它的源码和苹果的很像。
早在 1985 年, Steve Jobs 离开苹果电脑 (Apple) 后成立了 NeXT 公司, 并于 1988 年推出了 NeXT 电脑, 使用 NeXTStep 为作业系统. 在当时, NeXTStep 是相当先进的系统. 以 Unix (BSD) 为基础, 使用 PostScript 提供高品质的使用者图形介面, 并以 Objective-C 语言提供完整的物件导向环境.
尽管 NeXT 在软体上的优异, 其硬体销售成绩不佳, 不久之后, NeXT 便转型为软体公司. 1994 年, NeXT 与升阳 (Sun Microsystem) 合作推出 OpenStep 界面, 目标为跨平台的物件导向程式开发环境. NeXT 接着推出实作 OpenStep 介面的 OPENSTEP 系统, 可在 Mach, Microsoft Windows NT, Sun Solaris 及 HP/UX 上执行. 1996 年, 苹果电脑买下 NeXT, 做为苹果电脑下一代作业系统的基础, OPENSTEP 系统便演进成为 MacOS X 的 Cocoa 环境.
在 1995 年, 自由软体基金会 (Free Software Fundation) 开始了 GNUstep 计划, 目的在实作 OpenStep 介面, 以提供 Linux/BSD 系统一个完整的程式发展环境. 但由于 OpenStep 界面过于庞大, 开发人力不足, 及许多技术在当时尚未成熟 (如 Display PostScript), 所以直到目前为止, GNUstep 才算是一个完整的程式开发环境.
我们从基本的开始,写OC的代码:
- (void)viewDidLoad {
[super viewDidLoad];
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"KVO OC");
}
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"name"];
}
-
(一)添加观察者
- 我们直接从添加观察者开始,在GNUstep源码全局搜索并找到相应方法:
@implementation NSObject (NSKeyValueObserverRegistration)
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
GSKVOInfo *info;
GSKVOReplacement *r;
NSKeyValueObservationForwarder *forwarder;
NSRange dot;
setup();//获取 baseClass: GSKVOBase
[kvoLock lock];
r = replacementForClass([self class]);//创建子类并注册
info = (GSKVOInfo*)[self observationInfo];
if (info == nil)//判断是否交换过
{
info = [[GSKVOInfo alloc] initWithInstance: self];//创建GSKVOInfo
[self setObservationInfo: info];//设置GSKVOInfo
object_setClass(self, [r replacement]);//切换isa
}
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];//替换原来的setter方法
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: aContext];
}
[kvoLock unlock];
}
- 首先调用
setup();
,这里面会获取到一个GSKVOBase
类:
static inline void
setup()
{
if (nil == kvoLock)
{
[gnustep_global_lock lock];
if (nil == kvoLock)
{
...
baseClass = NSClassFromString(@"GSKVOBase");
}
[gnustep_global_lock unlock];
}
}
- 然后通过
replacementForClass([self class]);
创建子类并注册:
static GSKVOReplacement *
replacementForClass(Class c)
{
...
r = (GSKVOReplacement*)NSMapGet(classTable, (void*)c);//从列表获取
if (r == nil)//没有就创建
{
r = [[GSKVOReplacement alloc] initWithClass: c];
NSMapInsert(classTable, (void*)c, (void*)r);
}
[kvoLock unlock];
return r;
}
@implementation GSKVOReplacement
- (id) initWithClass: (Class)aClass
{
...
original = aClass;
superName = NSStringFromClass(original);
name = [@"GSKVO" stringByAppendingString: superName];
template = GSObjCMakeClass(name, superName, nil); //创建子类
GSObjCAddClasses([NSArray arrayWithObject: template]);//注册
replacement = NSClassFromString(name);
GSObjCAddClassBehavior(replacement, baseClass);//添加GSKVObase的重写方法
keys = [NSMutableSet new];
return self;
}
- 这里先是调用
GSObjCMakeClass(name, superName, nil);
创建子类:
NSValue *
GSObjCMakeClass(NSString *name, NSString *superName, NSDictionary *iVars)
{
...
classSuperClass = NSClassFromString(superName);
...
classNameCString = [name UTF8String];
//创建新类
newClass = objc_allocateClassPair(classSuperClass, classNameCString, 0);
if ([iVars count] > 0)
{
...
//添加变量
if (NO
== class_addIvar(newClass, iVarName, iVarSize, iVarAlign, iVarType))
{ ... }
}
}
return [NSValue valueWithPointer: newClass];
}
- 然后通过
GSObjCAddClasses([NSArray arrayWithObject: template]);
进行注册:
void
GSObjCAddClasses(NSArray *classes)
{
NSUInteger numClasses = [classes count];
NSUInteger i;
for (i = 0; i < numClasses; i++)
{
objc_registerClassPair((Class)[[classes objectAtIndex: i] pointerValue]);//注册
}
}
- 接下来回到第3点,下一步就是通过
GSObjCAddClassBehavior(replacement, baseClass);
添加GSKVObase
的方法:
void
GSObjCAddClassBehavior(Class receiver, Class behavior)
{
...
methods = class_copyMethodList(behavior, &count);//GSKVOBase方法列表
if (methods == NULL)
{ ... }
else
{
GSObjCAddMethods (receiver, methods, NO);//添加方法
free(methods);
}
...
}
void
GSObjCAddMethods(Class cls, Method *list, BOOL replace)
{
...
while ((m = list[index++]) != NULL)
{
SEL n = method_getName(m);
IMP i = method_getImplementation(m);
const char *t = method_getTypeEncoding(m);
//添加方法
if (YES == class_addMethod(cls, n, i, t))
{ ... }
else if (YES == replace)
{ ... }
else
{ ... }
}
}
这里可以先看一下GSKVObase
的方法:
@implementation GSKVOBase
- (void) dealloc { ... }
- (Class) class { ... }
- (void) setValue: (id)anObject forKey: (NSString*)aKey { ... }
- (void) takeStoredValue: (id)anObject forKey: (NSString*)aKey { ... }
- (void) takeValue: (id)anObject forKey: (NSString*)aKey { ... }
- (void) takeValue: (id)anObject forKeyPath: (NSString*)aKey { ... }
- (Class) superclass { ... }
@end
-
- 子类的准备完成后回到第1点,通过
object_setClass(self, [r replacement]);
切换isa
指向子类。
- 子类的准备完成后回到第1点,通过
-
- 接着会创建
GSKVOInfo
,GSKVOInfo
会保存很多信息,所以后面调用了[info addObserver: anObserver forKeyPath: aPath options: options context: forwarder];
,由原观察者进行迁移到GSKVOInfo
(观察者迁移),然后在里面做各种处理(context,options等)。
- 接着会创建
- 在后面还有一步,通过
[r overrideSetterFor: aPath];
替换原来的settter
方法(会根据对应类型替换成GSKVOSetter
的setter
):
@implementation GSKVOReplacement
- (void) overrideSetterFor: (NSString*)aKey
{
if ([keys member: aKey] == nil)
{
...
suffix = [aKey substringFromIndex: 1];//从第2个字母开始截取
u = uni_toupper([aKey characterAtIndex: 0]);//取首字母->大写
tmp = [[NSString alloc] initWithCharacters: &u length: 1];//char->String
a[0] = [NSString stringWithFormat: @"set%@%@:", tmp, suffix]//拼接成相应setter方法
a[1] = [NSString stringWithFormat: @"_set%@%@:", tmp, suffix];
[tmp release];
for (i = 0; i < 2; i++)
{
/*
* Replace original setter with our own version which does KVO
* notifications.
*/
sel = NSSelectorFromString(a[i]);//方法选择器
...
sig = [original instanceMethodSignatureForSelector: sel];//监听对象的方法
...
type = [sig getArgumentTypeAtIndex: 2];
switch (*type)
{
case _C_CHR:
case _C_UCHR:
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setterChar:)];//其他类型操作一样
break;
case _C_SHT:
case _C_USHT: ...
case _C_INT:
case _C_UINT: ...
case _C_LNG:
case _C_ULNG: ...
case _C_FLT: ...
case _C_DBL: ...
case _C_BOOL: ...
case _C_ID:
case _C_CLASS:
case _C_PTR:
imp = [[GSKVOSetter class]
instanceMethodForSelector: @selector(setter:)];
break;
case _C_STRUCT_B: ...
default: ...
}
if (imp != 0)
{
//添加方法;属性setter方法选择器关联上面的imp指针
if (class_addMethod(replacement, sel, imp, [sig methodType]))
{ ... }
else
{ ... }
}
}
...
}
-
属性setter方法选择器
关联上面的imp
指针,执行时对应类型属性调用上面的方法。
-
(二)监听响应
- 一切准备好后,当值改变时,将会走
setter
方法,但是由于之前切换了isa
,所以会来到这里:
@implementation GSKVOSetter
- (void) setterChar: (unsigned char)val
{
NSString *key;
Class c = [self class];
void (*imp)(id,SEL,unsigned char);
//_cmd是SEL
imp = (void (*)(id,SEL,unsigned char))[c instanceMethodForSelector: _cmd];//获取监听对象属性setter的原imp指针
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES)
{
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);//执行原setter方法
// post setting code here
[self didChangeValueForKey: key];
}
else
{
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
- (void) setterDouble: (double)val{ ... }
...//还有其他类型
或者用setValue:forKey:
/setValue:forKeyPath:
赋值时:
@implementation GSKVOBase
- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
...
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];//原来多了这个
imp(self,_cmd,anObject,aKey);
[self didChangeValueForKey: aKey];
}
else
{
imp(self,_cmd,anObject,aKey);
}
}
- 它们都会调用
willChangeValueForKey
:
@implementation NSObject (NSKeyValueObserverNotification)
- (void) willChangeValueForKey: (NSString*)aKey
{
...
if (pathInfo != nil)
{
if (pathInfo->recursion++ == 0)
{
...
if (old != nil)
{ ... }
else if (pathInfo->allOptions & NSKeyValueObservingOptionOld)
{ ... }
...
//通知响应
[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES];
}
[info unlock];
}
[self willChangeValueForDependentsOfKey: aKey];
}
@implementation GSKVOPathInfo
- (void) notifyForKey: (NSString *)aKey ofInstance: (id)instance prior: (BOOL)f
{
...
while (count-- > 0)
{
GSKVOObservation *o = [observations objectAtIndex: count];
if (f == YES)
{ ... }
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];//回调
}
...
}
保存新旧值后,通过[o->observer observeValueForKeyPath: aKey ofObject: instance change: change context: o->context];
进行回调出去。
原来KVO就是在setter方法的底层源码里加了回调。
(突然好开心,没想到当年说的好傻的答案起码方向是对的)
-
(三)移除观察者
- 移除观察者就是反着来:
@implementation NSObject (NSKeyValueObserverRegistration)
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
...
[info removeObserver: anObserver forKeyPath: aPath];//
if ([info isUnobserved] == YES)
{
object_setClass(self, [self class]);//把isa换回来
IF_NO_GC(AUTORELEASE(info);)
[self setObservationInfo: nil];//
}
...
}
- 总结
KVO的原理就是,通过在底层创建新的子类,切换监听对象的
isa
指向子类,交换属性setter
时执行的imp
指针,属性赋值时,执行新的set
系列方法,然后会调用willChangeValueForKey
走向监听回调。
-
补充
其实Swift的源码有一部分开源了,所以我们也可以看看相关的:
- 在Swift项目里使用KVO要加上
@objc
,因为底层走的还是OC的那套方法。