问题
1.iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
答. 当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改为指向一个全新的通过Runtime动态创建的NSKVONotifying_Person
子类,子类拥有自己的set方法实现,set方法实现内部会顺序调用willChangeValueForKey
方法、原来的setter
方法实现、didChangeValueForKey
方法,而didChangeValueForKey
方法内部又会调用监听者的observeValueForKeyPath:ofObject:change:context:
监听方法。
2.如何手动触发KVO?
答. 被监听的属性的值被修改时,就会自动触发KVO。如果想要手动触发KVO,则需要我们自己调用willChangeValueForKey
和didChangeValueForKey
方法即可在不改变属性值的情况下手动触发KVO,并且这两个方法缺一不可。
- KVO的全称 Key-Value Observing(俗称“键值监听”),可以用于监听某个对象属性值的改变。基于Runtime
首先贴出Swift源码下载地址,通过Swift源码分析。
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)
}
}
问题来了,这两种使用方式有什么关系?带着这个问题我们来进行探索。
关系
拿到了Swift源码,但是如何找到跟KVO相关的demo,一脸懵逼。。。
想一下,对任意的对象都可以使用KVO,所以大胆猜测一下,相关的方法应该会写在NSObject的类里面。然后通过搜索addObserver:
确实发现NSObject.swift
文件,打开文件查看:
public struct NSKeyValueObservedChange<Value> {
public typealias Kind = NSKeyValueChange
public let kind: Kind
///newValue and oldValue will only be non-nil if .new/.old is passed to `observe()`. In general, get the most up to date value by accessing it directly on the observed object instead.
public let newValue: Value?
public let oldValue: Value?
///indexes will be nil unless the observed KeyPath refers to an ordered to-many property
public let indexes: IndexSet?
///'isPrior' will be true if this change observation is being sent before the change happens, due to .prior being passed to `observe()`
public let isPrior:Bool
}
这段代码明显就是KVO监听值变化的相关状态
继续往下找发现了NSKeyValueObservingCustomization
协议:
public protocol NSKeyValueObservingCustomization : NSObjectProtocol {
static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath>
static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool
}
1、keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath>
通过一个key观察多个属性值的改变
2、automaticallyNotifiesObservers (for key: AnyKeyPath) -> Bool
想要手动调用或自己实现KVO需要重写该方法该方法返回YES表示可以调用,返回NO则表示不可以调用。
继续往下走。。。此处省略很多代码,走到最后发现了如下代码:
public func willChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>) {
(self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath))
}
public func willChange<Value>(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: __owned KeyPath<Self, Value>) {
(self as! NSObject).willChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
}
public func willChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set<Value>) -> Void {
(self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
}
public func didChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>) {
(self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath))
}
public func didChange<Value>(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: __owned KeyPath<Self, Value>) {
(self as! NSObject).didChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
}
public func didChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set<Value>) -> Void {
(self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
}
这里我们比较关心willChangeValue
和didChangeValue
这两个方法。
既然这两个方法比较重要,那么我们再来搜素一下willChangeValue
和didChangeValue
。又发现了一个文件KVOKeyPaths.swift
,打开查看:
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)
}
}
@objc dynamic var objcValue3: String
// This Swift-typed property causes vtable usage on this class.
var swiftValue: Guts
override init() {
self.swiftValue = Guts()
self.objcValue = ""
self.objcValue2 = ""
self.objcValue3 = ""
super.init()
}
static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath> {
if (key == \Target.objcValue) {
return [\Target.objcValue2, \Target.objcValue3]
} else {
return []
}
}
static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool {
if key == \Target.objcValue2 || key == \Target.objcValue3 {
return false
}
return true
}
func print() {
Swift.print("swiftValue \(self.swiftValue.value), objcValue \(objcValue)")
}
}
1、发现这个Target
遵守了NSKeyValueObservingCustomization
协议并且实现了协议的两个方法。
2、计算型属性在willSet里面就调用willChangeValue(for: \.objcValue2)
,didSet调用didChangeValue(for: \.objcValue2)
,这里说明计算型属性是和KVO相关方法有所关联的!
底层
我们都知道苹果底层并不开源,所以真正的底层源码是无法探究到的。但是我们可以从GNUstep源码来了解一下,这里贴出GNUStep源码地址。
拿到GNUstep源码
-
第一步:添加观察
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
// 使用初始类Persion
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([self class])
这句代码内部创建了一个子类 -
info = (GSKVOInfo*)[self observationInfo]
获取观察信息 -
[info addObserver: anObserver forKeyPath: aPath options: options context: aContext]
添加监听者
1、如何创建子类
查看GSKVOReplacement
类的初始化方法:
- (id) initWithClass: (Class)aClass
{
NSValue *template;
NSString *superName;
NSString *name;
...
ginal = aClass;
/*
* Create subclass of the original, and override some methods
* with implementations from our abstract base class.
* 创建子类,并且在基类里重写了一些方法
*/
superName = NSStringFromClass(original);
name = [@"GSKVO" stringByAppendingString: superName];
template = GSObjCMakeClass(name, superName, nil);
GSObjCAddClasses([NSArray arrayWithObject: template]);
replacement = NSClassFromString(name);
GSObjCAddClassBehavior(replacement, baseClass);
/* Create the set of setter methods overridden.
* 创建 setter方法重写 集合
*/
keys = [NSMutableSet new];
return self;
}
- 1、
GSObjCMakeClass
:创建了名字为GSKVOPerson
的子类 - 2、
GSObjCAddClasses
:注册类 - 3、
GSObjCAddClassBehavior(replacement, baseClass)
:添加了基类GSKVOBase
的相关方法
原类的isa与动态isa切换
我们来看一下GSObjCAddClassBehavior(replacement, baseClass)
函数内的实现:
void
GSObjCAddClassBehavior(Class receiver, Class behavior)
{
...
...
...
/* Add instance methods */
methods = class_copyMethodList(behavior, &count);
GSObjCAddMethods (receiver, methods, NO);
free(methods);
/* Add class methods */
methods = class_copyMethodList(object_getClass(behavior), &count);
GSObjCAddMethods (object_getClass(receiver), methods, NO);
free(methods);
/* Add behavior's superclass, if not already there. */
if (!GSObjCIsKindOf(receiver, behavior_super_class))
{
GSObjCAddClassBehavior (receiver, behavior_super_class);
}
GSFlushMethodCacheForClass (receiver);
}
我们发现会调用GSObjCAddMethods(Class cls, Method *list, BOOL replace)
函数
void
GSObjCAddMethods(Class cls, Method *list, BOOL replace)
{
unsigned int index = 0;
char c;
Method m;
c = class_isMetaClass(cls) ? '+' : '-';
while ((m = list[index++]) != NULL)
{
SEL n = method_getName(m);
IMP i = method_getImplementation(m);
const char *t = method_getTypeEncoding(m);
/* This will override a superclass method but will not replace a
* method which already exists in the class itsself.
* 覆盖父类方法,但是如果当前类已经实现了该方法就不再替换
*/
if (YES == class_addMethod(cls, n, i, t)){
BDBGPrintf(" added %c%s\n", c, sel_getName(n));
} else if (YES == replace) {
/* If we want to replace an existing implemetation ...
* 替换方法的实现
*/
method_setImplementation(class_getInstanceMethod(cls, n), i);
BDBGPrintf(" replaced %c%s\n", c, sel_getName(n));
} else {
BDBGPrintf(" skipped %c%s\n", c, sel_getName(n));
}
}
}
到这里我们应该明白了,是如何创建子类的。
2、添加监听者
首先来看一下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; }
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
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;
}
// 如果添加监听者时设置包含 NSKeyValueObservingOptionInitial就会立即执行
if (options & NSKeyValueObservingOptionInitial)
{
[pathInfo->change setObject: [NSNumber numberWithInt: 1]
forKey: NSKeyValueChangeKindKey];
[anObserver observeValueForKeyPath: aPath
ofObject: instance
change: pathInfo->change
context: aContext];
}
}
-
1、先判断监听者有没有实现监听方法
-
2、
GSKVOPathInfo
类型的pathInfo
存储path
信息 -
3、
while
循环取出当前观察者 -
4、如果添加监听者时设置包含
NSKeyValueObservingOptionInitial
就会立即执行监听方法 -
第二步:观察属性值的改变
我们通过搜索willChangeValueForKey :
找到如下代码:
- (void) willChangeValueForKey: (NSString*)aKey
{
GSKVOPathInfo *pathInfo;
GSKVOInfo *info;
// 获取监听信息
info = (GSKVOInfo *)[self observationInfo];
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
的值设置为NSKeyValueChangeOldKey
-
[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES]
发通知
查看GSKVOPathInfo
类内notifyForKey: ofInstance:prior:
方法的代码实现:
- (void) notifyForKey: (NSString *)aKey ofInstance: (id)instance prior: (BOOL)f
{
unsigned count;
id oldValue;
id newValue;
if (f == YES) {
if ((allOptions & NSKeyValueObservingOptionPrior) == 0) { return; }
[change setObject: [NSNumber numberWithBool: YES]
forKey: NSKeyValueChangeNotificationIsPriorKey];
} else {
[change removeObjectForKey: NSKeyValueChangeNotificationIsPriorKey];
}
oldValue = [[change objectForKey: NSKeyValueChangeOldKey] retain];
newValue = [[change objectForKey: NSKeyValueChangeNewKey] retain];
/* 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 ((o->options & NSKeyValueObservingOptionPrior) == 0) { continue; }
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
集合 设置/获取 一些数据 -
[o->observer observeValueForKeyPath: aKey ofObject: instance change: change context: o->context];
监听者响应监听方法。
同样在didChangeValueForKey:
方法内也调用了[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: NO];
方法
到这里三部曲已经走完两部了,还剩下最后一步移除监听者:
第三步,移除监听者
- (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.
* 关闭KVO
*/
object_setClass(self, [self class]);
IF_NO_GC(AUTORELEASE(info);)
[self setObservationInfo: nil];
}
if ([aPath rangeOfString:@"."].location != NSNotFound)
[forwarder finalize];
}
-
[info removeObserver: anObserver forKeyPath: aPath];
info
移除监听者 -
object_setClass(self, [self class]);
动态子类的isa和原类的isa切换回来
`GSKVOInfo `类内的移除方法:
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
GSKVOPathInfo *pathInfo;
[iLock lock];
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
if (pathInfo != nil)
{
unsigned count = [pathInfo->observations count];
pathInfo->allOptions = 0;
while (count-- > 0)
{
GSKVOObservation *o;
o = [pathInfo->observations objectAtIndex: count];
if (o->observer == anObserver || o->observer == nil)
{
[pathInfo->observations removeObjectAtIndex: count];
if ([pathInfo->observations count] == 0)
{
NSMapRemove(paths, (void*)aPath);
}
}
else
{
pathInfo->allOptions |= o->options;
}
}
}
[iLock unlock];
}
-
[pathInfo->observations removeObjectAtIndex: count];
移除监听者 -
NSMapRemove(paths, (void*)aPath);
移除keyPath
网友评论