KVO的实现原理是利用runtime动态生成一个被观察对象的子类,重新子类的4个方法,实现通知监听者。
一。子类的生成
1.runtime动态生成类
先调用 objc_allocateClassPair函数
/* Adding Classes */
/**
* Creates a new class and metaclass.
*
* @param superclass The class to use as the new class's superclass, or \c Nil to create a new root class.
* @param name The string to use as the new class's name. The string will be copied.
* @param extraBytes The number of bytes to allocate for indexed ivars at the end of
* the class and metaclass objects. This should usually be \c 0.
*
* @return The new class, or Nil if the class could not be created (for example, the desired name is already in use).
*
* @note You can get a pointer to the new metaclass by calling \c object_getClass(newClass).
* @note To create a new class, start by calling \c objc_allocateClassPair.
* Then set the class's attributes with functions like \c class_addMethod and \c class_addIvar.
* When you are done building the class, call \c objc_registerClassPair. The new class is now ready for use.
* @note Instance methods and instance variables should be added to the class itself.
* Class methods should be added to the metaclass.
*
* 创建一个类或者元类
* superclass: 父类的名称
* name : 创建的新的类名
* extraBytes:ivars 分配的字节
*
* 如果类名已经存在,则返回nil,否则返回新的class
*
* class_addMethod 添加方法
* class_addIvar 添加成员变量
* 执行完成之后需要调用objc_registerClassPair函数
*/
OBJC_EXPORT Class _Nullable
objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name,
size_t extraBytes)
/***********************************************************************
* objc_allocateClassPair
* fixme
* Locking: acquires runtimeLock
* 动态创建一个类
**********************************************************************/
Class objc_allocateClassPair(Class superclass, const char *name,
size_t extraBytes)
{
Class cls, meta;
const char *superName = object_getClassName(superclass);
printf("\n objc_allocateClassPair: 父类:%s-----子类:%s\n",superName,name);
// Fail if the class name is in use.
// 查找名称为name的类是否存在,如果存在,返回nil
if (look_up_class(name, NO, NO)) return nil;
mutex_locker_t lock(runtimeLock);
// Fail if the class name is in use.
// Fail if the superclass isn't kosher.
// 父类非法,返回nil
if (getClassExceptSomeSwift(name) ||
!verifySuperclass(superclass, true/*rootOK*/))
{
return nil;
}
// Allocate new classes.
// 创建一个新类
cls = alloc_class_for_subclass(superclass, extraBytes);
meta = alloc_class_for_subclass(superclass, extraBytes);
// fixme mangle the name if it looks swift-y?
objc_initializeClassPair_internal(superclass, name, cls, meta);
return cls;
}
1.1先调用 objc_allocateClassPair函数中调用look_up_class,判断需要创建的类是否存在
Class
look_up_class(const char *name,
bool includeUnconnected __attribute__((unused)),
bool includeClassHandler __attribute__((unused)))
{
if (!name) return nil;
Class result;
bool unrealized;
{
runtimeLock.lock();
result = getClassExceptSomeSwift(name);
// 判断name是否存在
unrealized = result && !result->isRealized();
if (unrealized) {
result = realizeClassMaybeSwiftAndUnlock(result, runtimeLock);
// runtimeLock is now unlocked
} else {
runtimeLock.unlock();
}
}
if (!result) {
// Ask Swift about its un-instantiated classes.
// We use thread-local storage to prevent infinite recursion
// if the hook function provokes another lookup of the same name
// (for example, if the hook calls objc_allocateClassPair)
auto *tls = _objc_fetch_pthread_data(true);
// Stop if this thread is already looking up this name.
for (unsigned i = 0; i < tls->classNameLookupsUsed; i++) {
if (0 == strcmp(name, tls->classNameLookups[i])) {
return nil;
}
}
// Save this lookup in tls.
if (tls->classNameLookupsUsed == tls->classNameLookupsAllocated) {
tls->classNameLookupsAllocated =
(tls->classNameLookupsAllocated * 2 ?: 1);
size_t size = tls->classNameLookupsAllocated *
sizeof(tls->classNameLookups[0]);
tls->classNameLookups = (const char **)
realloc(tls->classNameLookups, size);
}
tls->classNameLookups[tls->classNameLookupsUsed++] = name;
// Call the hook.
Class swiftcls = nil;
if (GetClassHook.get()(name, &swiftcls)) {
ASSERT(swiftcls->isRealized());
result = swiftcls;
}
// Erase the name from tls.
unsigned slot = --tls->classNameLookupsUsed;
ASSERT(slot >= 0 && slot < tls->classNameLookupsAllocated);
ASSERT(name == tls->classNameLookups[slot]);
tls->classNameLookups[slot] = nil;
}
return result;
}
1.2 调用 objc_registerClassPair函数
/***********************************************************************
* objc_registerClassPair
* fixme
* Locking: acquires runtimeLock
注册一个类
**********************************************************************/
void objc_registerClassPair(Class cls)
{
const char *clsName = object_getClassName(cls);
if (strstr(clsName, "NSKVONotifying_Person") ) {
printf("\nobjc_registerClassPair:%s\n",clsName);
}
mutex_locker_t lock(runtimeLock);
checkIsKnownClass(cls);
//如果类已经存在,直接返回
if ((cls->data()->flags & RW_CONSTRUCTED) ||
(cls->ISA()->data()->flags & RW_CONSTRUCTED))
{
_objc_inform("objc_registerClassPair: class '%s' was already "
"registered!", cls->data()->ro()->name);
return;
}
// 必须调用过objc_allocateClassPair
if (!(cls->data()->flags & RW_CONSTRUCTING) ||
!(cls->ISA()->data()->flags & RW_CONSTRUCTING))
{
_objc_inform("objc_registerClassPair: class '%s' was not "
"allocated with objc_allocateClassPair!",
cls->data()->ro()->name);
return;
}
// Clear "under construction" bit, set "done constructing" bit
cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
// Add to named class table.
// 添加到table中
addNamedClass(cls, cls->data()->ro()->name);
}
经过上面两步,一个新的子类会创建
二.KVO动态生成子类堆栈分析
在main函数中的测试代码
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Observer.h"
Observer *observer;
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@" 进入 main 函数");
observer = [[Observer alloc] init];
Person *p = [[Person alloc] init];
[p addObserver:observer forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
//
// sleep(5);
// [p removeObserver:observer forKeyPath:@"name"];
}
return 0;
}
给person对象的name属性添加KVO监听
在 objc_allocateClassPair函数中打上断点,可以看到如下堆栈
如图01所示:
当给一个类添加kvo后,runtime动态生成类的堆栈
- [NSObject(NSKeyValueObserverRegistration) addObserver:forKeyPath:options:context:]
- [NSObject(NSKeyValueObserverRegistration) _addObserver:forProperty:options:context:]:
- NSKeyValueUnnestedProperty isaForAutonotifying]
- [NSKeyValueUnnestedProperty _isaForAutonotifying]
5._NSKeyValueContainerClassGetNotifyingInfo
- _NSKVONotifyingCreateInfoWithOriginalClass
- objc_allocateClassPair
第6步的汇编代码
02.png
__NSSetObjectValueAndNotify 伪代码
//伪代码,仅供理解
void __NSSetObjectValueAndNotify(id self, SEL _cmd, id value) {
//获取额外的变量
void *indexedIvars = object_getIndexedIvars(object_getClass(self));
//加锁
pthread_mutex_lock(indexedIvars + 0x20);
//从SEL获取KeyPath
NSString *keyPath = [CFDictionaryGetValue(*(indexedIvars) + 0x18), _cmd) copyWithZone:0x0];
//解锁
pthread_mutex_unlock(indexedIvars + 0x20);
//改变前发通知
[self willChangeValueForKey:keyPath];
//实现Setter方法
IMP imp = class_getMethodImplementation(*indexedIvars, _cmd);
(imp)(self, _cmd, value);
//改变后发通知
[self didChangeValueForKey:keyPath];
}
[NSObject addObserver:forKeyPath:options:context:] 伪代码
//伪代码,仅供理解
void -[NSObject addObserver:forKeyPath:options:context:]
(void * self, void * _cmd, void * arg2, void * arg3, unsigned long long arg4, void * arg5) {
pthread_mutex_lock(__NSKeyValueObserverRegistrationLock);
*__NSKeyValueObserverRegistrationLockOwner = pthread_self();
rax = object_getClass(self);
rax = _NSKeyValuePropertyForIsaAndKeyPath(rax, arg3);
[self _addObserver:arg2 forProperty:rax options:arg4 context:arg5];
*__NSKeyValueObserverRegistrationLockOwner = 0x0;
pthread_mutex_unlock(__NSKeyValueObserverRegistrationLock);
return;
}
//伪代码,仅供理解
- (void *)_addObserver:(id)observer
forProperty:(NSKeyValueProperty *)property
options:(NSKeyValueObservingOptions)option
context:(void *)context {
//需要注册通知
if (option & NSKeyValueObservingOptionInitial) {
//获取属性名路径
NSString *keyPath = [property keyPath];
//解锁
pthread_mutex_unlock(__NSKeyValueObserverRegistrationLock);
//如果注册了获得新值,就获取数值
id value = nil;
if (option & NSKeyValueObservingOptionNew) {
value = [self valueForKeyPath:keyPath];
if (value == nil) {
value = [NSNull null];
}
}
//发送注册通知
_NSKeyValueNotifyObserver(observer, keyPath, self, context, value,
0 /*originalObservable*/, 1 /*NSKeyValueChangeSetting*/);
//加锁
pthread_mutex_lock(__NSKeyValueObserverRegistrationLock);
}
//获取属性的观察信息
Info *info = __NSKeyValueRetainedObservationInfoForObject(self, property->_containerClass);
//判断是否需要获取新的数值
id _additionOriginalObservable = nil;
if (option & NSKeyValueObservingOptionNew) {
//0�x15没有找到定义,猜测为保存是否可观察的数组
id tsd = _CFGetTSD(0x15);
if (tsd != nil) {
_additionOriginalObservable = *(tsd + 0x10);
}
}
//在原有信息上生成新的信息
Info *newInfo = __NSKeyValueObservationInfoCreateByAdding
(info, observer, property, option, context, _additionOriginalObservable, 0, 1);
//替换属性的观察信息
__NSKeyValueReplaceObservationInfoForObject(self, property->_containerClass, info, newInfo);
//属性添加后递归添加关联属性
[property object:self didAddObservance:newInfo recurse:true];
//获取新的isa
// 如果一个对象是第一次被观察,需要替换isa对象
Class cls = [property isaForAutonotifying];
if ((cls != NULL) && (object_getClass(self) != cls)) {
//如果是第一次就替换isa
object_setClass(self, cls);
}
//释放观察信息
[newInfo release];
if (info != nil) {
[info release];
}
return;
}
03.png
给一个类动态的添加method
// 给一个类添加方法
BOOL
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;
mutex_locker_t lock(runtimeLock);
return ! addMethod(cls, name, imp, types ?: "", NO);
}
04.png
如图4所示,动态添加了_isKVOA
三. GNU addObserver 实现
// 添加观察者
- (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];
// 如果info 为空
if (info == nil)
{
//创建观察者信息
info = [[GSKVOInfo alloc] initWithInstance: self];
// 保存观察者信息
[self setObservationInfo: info];
// [r replacement] 就是子类,将self设置为子类类型(修改isa指向)
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
{
// 重新setter方法
[r overrideSetterFor: aPath];
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: aContext];
}
[kvoLock unlock];
}
三移除KVO
[p removeObserver:observer forKeyPath:@"name"];
06
如图6所示:当移除KVO时,如果该对象没有监听者,会销毁NSKeyValueObservance
释放对象
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
GNU removeObserver 实现
/*
* removes the observer
移除监听者
*/
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
GSKVOPathInfo *pathInfo;
[iLock lock];
// 获取观察者信息
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
if (pathInfo != nil)
{
// 移除path对应的所有观察者
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];
}
observer = [[Observer alloc] init];
Person *p = [[Person alloc] init];
[p addObserver:observer forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
[p removeObserver:observer forKeyPath:@"name"];
Class cls = objc_lookUpClass("NSKVONotifying_Person");
const char *clsName = object_getClassName(cls);
NSLog(@" ------- %s",clsName);
打印结果-------NSKVONotifying_Person
说明移除KVO之后,NSKVONotifying_Person没有被销毁
NSKVODeallocate 实现
使用hopper查看苹果实现
int _NSKVODeallocate(int arg0, int arg1) {
r13 = rdi;
var_-48 = **___stack_chk_guard;
rax = object_getClass(rdi);
r12 = __NSKVOUsesBaseClassObservationInfoImplementationForClass(rax);
rax = object_getIndexedIvars(rax);
r14 = rax;
rbx = class_getInstanceMethod(*rax, rsi);
if (r12 == 0x0) goto loc_7fff207b448e;
loc_7fff207b4461:
if (**___stack_chk_guard == var_-48) {
rdi = r13;
rsi = rbx;
rax = method_invoke(rdi, rsi);
}
else {
rax = __stack_chk_fail();
}
return rax;
loc_7fff207b448e:
rax = __NSKeyValueRetainedObservationInfoForObject(r13, 0x0);
*var_-72 = r13;
*(var_-72 + 0x8) = rax;
*(var_-72 + 0x10) = 0x0;
__NSKeyValueAddObservationInfoWatcher(var_-72);
r12 = __NSKVOObservationInfoOverridenObjectMayThrowOnDealloc(r13);
method_invoke(r13, rbx);
if (var_-64 == 0x0) goto loc_7fff207b4570;
loc_7fff207b44d1:
r15 = dyld_get_program_sdk_version();
if (r12 != 0x0) {
r12 = (*_objc_msgSend)(var_-64, *0x7fff86b9d448) ^ 0x1;
}
else {
r12 = 0x0;
}
*(int8_t *)var_-73 = 0x0;
rax = CFPreferencesGetAppBooleanValue(@"NSKVODeallocateCleansUpBeforeThrowing", **_kCFPreferencesCurrentApplication, var_-73);
rcx = 0x0;
CMP(r15, 0x7ffff);
rdx = r12 & 0xff;
rsi = 0x0;
asm{ cmova esi, edx };
rbx = (var_-73 == rcx ? 0x1 : 0x0) | (rax == 0x0 ? 0x1 : 0x0);
if (rbx == 0x0) {
rsi = rdx;
}
if (rsi != 0x0) goto loc_7fff207b45b4;
loc_7fff207b4542:
if ((r15 < 0x80000) || (r12 != 0x0)) {
_NSLog(@"An instance %p of class %@ was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debu…", r13, *r14);
_NSKVODeallocateBreak(r13);
}
goto loc_7fff207b4570;
loc_7fff207b4570:
__NSKeyValueRemoveObservationInfoWatcher(var_-72);
[var_-64 release];
if (0x0 == 0x0) {
rax = *___stack_chk_guard;
rax = *rax;
if (rax != var_-48) {
rax = __stack_chk_fail();
}
}
else {
rax = objc_exception_rethrow();
}
return rax;
loc_7fff207b45b4:
r15 = (*_objc_msgSend)(var_-64, *0x7fff86b9a5e8);
if (rbx == 0x0) {
__NSKeyValueRemoveObservationInfoForObject(var_-72);
}
rax = (*_objc_msgSend)(@class(NSString), *0x7fff86b9a4b8);
rax = (*_objc_msgSend)(@class(), *0x7fff86b9a700);
rax = objc_exception_throw(rax);
return rax;
}
DIS_KVC_KVO的DSKVODeallocate实现
void DSKVODeallocate(id object, SEL selector) {
DSKeyValueObservationInfo *observationInfo = _DSKeyValueRetainedObservationInfoForObject(object, nil);
ObservationInfoWatcher watcher = {object, observationInfo, NULL};
_DSKeyValueAddObservationInfoWatcher(&watcher);
DSKeyValueNotifyingInfo *notifyInfo = (DSKeyValueNotifyingInfo *)object_getIndexedIvars(object_getClass(object));
Method originDellocMethod = class_getInstanceMethod(notifyInfo->originalClass, selector);
((id (*)(id,Method))method_invoke)(object, originDellocMethod);
@try {
if(watcher.observationInfo) {
BOOL keyExistsAndHasValidFormat = false;
BOOL cleansUpBeforeThrowing = false;
cleansUpBeforeThrowing = (BOOL)CFPreferencesGetAppBooleanValue(CFSTR("NSKVODeallocateCleansUpBeforeThrowing"), kCFPreferencesCurrentApplication, (Boolean *)&keyExistsAndHasValidFormat);
cleansUpBeforeThrowing = cleansUpBeforeThrowing && keyExistsAndHasValidFormat;
if (dyld_get_program_sdk_version() > 0x7FFFF || cleansUpBeforeThrowing) {
if (cleansUpBeforeThrowing) {
_DSKeyValueRemoveObservationInfoForObject(object, watcher.observationInfo);
}
[NSException raise:NSInternalInconsistencyException format:@"An instance %p of class %@ was deallocated while key value observers were still registered with it. Current observation info: %@", object, notifyInfo->originalClass, watcher.observationInfo];
}
else {
NSLog(@"An instance %p of class %@ was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:\n%@", object, notifyInfo->originalClass, watcher.observationInfo);
DSKVODeallocateBreak(object);
}
}
}
@catch (NSException *exception) {
[exception raise];
}
@finally {
_DSKeyValueRemoveObservationInfoWatcher(&watcher);
[watcher.observationInfo release];
}
}
kvo子类重写dealloc的目的?
存在这样一种情况,person对象已经被销毁了,但是观察者没有被移除。从上面的分析可以看到 NSKeyValueObservance就无法销毁。当person对象销毁时,会根据isa指针找到-dealloc 方法,由于观察者没有移除,isa指向 NSKVONotifying_Person类,这个类重写了-dealloc方法,会做一些清除kvo的操作,所以需要重新子类的-dealloc 方法。
KVO源码中添加观察者时整体的大致流程是什么?
- 将keyPath、class等信息封装成NSKeyValueProperty,分别解析一般属性(@”aa”)、可计算属性(@”@aa”)、属性链(@”aa.bb.@cc.dd“),进行子类化,缓存在CFMutableSet中方便下次快速取出。
- 将NSKeyValueProperty、context、options、observer等信息封装成NSKeyValueObservance,缓存在NSHashTable中。
- 倘若设置了NSKeyValueObservingOptionInitial选项,会在注册观察服务时调用一次触发方法。
- 动态创建名为NSKVONotifying_+原来类名的新类,重写其dealloc、_isKVOA方法,再重写class方法,利用object_setClass()函数将其isa指针指向原先的类。
- 重写willChangeValueForKey:和didChangeValueForKey:方法,重写被观察属性的setter方法,在setter中先调用willChangeValueForKey:方法,然后调用父类的 setter 方法对成员变量赋值,之后再调用 didChangeValueForKey: 方法。
- didChangeValueForKey: 方法中会调用observeValueForKeyPath:ofObject:change:context:方法。
网友评论