KVO的實(shí)現(xiàn)原理是利用runtime動(dòng)態(tài)生成一個(gè)被觀察對(duì)象的子類具篇,重新子類的4個(gè)方法纬霞,實(shí)現(xiàn)通知監(jiān)聽者。
一栽连。子類的生成
1.runtime動(dòng)態(tài)生成類
先調(diào)用 objc_allocateClassPair函數(shù)
/* 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.
*
* 創(chuàng)建一個(gè)類或者元類
* superclass: 父類的名稱
* name : 創(chuàng)建的新的類名
* extraBytes:ivars 分配的字節(jié)
*
* 如果類名已經(jīng)存在险领,則返回nil,否則返回新的class
*
* class_addMethod 添加方法
* class_addIvar 添加成員變量
* 執(zhí)行完成之后需要調(diào)用objc_registerClassPair函數(shù)
*/
OBJC_EXPORT Class _Nullable
objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name,
size_t extraBytes)
/***********************************************************************
* objc_allocateClassPair
* fixme
* Locking: acquires runtimeLock
* 動(dòng)態(tài)創(chuàng)建一個(gè)類
**********************************************************************/
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.
// 創(chuàng)建一個(gè)新類
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先調(diào)用 objc_allocateClassPair函數(shù)中調(diào)用look_up_class熔恢,判斷需要?jiǎng)?chuàng)建的類是否存在
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 調(diào)用 objc_registerClassPair函數(shù)
/***********************************************************************
* objc_registerClassPair
* fixme
* Locking: acquires runtimeLock
注冊(cè)一個(gè)類
**********************************************************************/
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);
//如果類已經(jīng)存在脐湾,直接返回
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;
}
// 必須調(diào)用過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);
}
經(jīng)過上面兩步,一個(gè)新的子類會(huì)創(chuàng)建
二.KVO動(dòng)態(tài)生成子類堆棧分析
在main函數(shù)中的測(cè)試代碼
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Observer.h"
Observer *observer;
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@" 進(jìn)入 main 函數(shù)");
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對(duì)象的name屬性添加KVO監(jiān)聽
在 objc_allocateClassPair函數(shù)中打上斷點(diǎn)叙淌,可以看到如下堆棧
如圖01所示:
當(dāng)給一個(gè)類添加kvo后秤掌,runtime動(dòng)態(tài)生成類的堆棧
- [NSObject(NSKeyValueObserverRegistration) addObserver:forKeyPath:options:context:]
- [NSObject(NSKeyValueObserverRegistration) _addObserver:forProperty:options:context:]:
- NSKeyValueUnnestedProperty isaForAutonotifying]
- [NSKeyValueUnnestedProperty _isaForAutonotifying]
5._NSKeyValueContainerClassGetNotifyingInfo
- _NSKVONotifyingCreateInfoWithOriginalClass
- objc_allocateClassPair
第6步的匯編代碼
__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);
//改變前發(fā)通知
[self willChangeValueForKey:keyPath];
//實(shí)現(xiàn)Setter方法
IMP imp = class_getMethodImplementation(*indexedIvars, _cmd);
(imp)(self, _cmd, value);
//改變后發(fā)通知
[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 {
//需要注冊(cè)通知
if (option & NSKeyValueObservingOptionInitial) {
//獲取屬性名路徑
NSString *keyPath = [property keyPath];
//解鎖
pthread_mutex_unlock(__NSKeyValueObserverRegistrationLock);
//如果注冊(cè)了獲得新值,就獲取數(shù)值
id value = nil;
if (option & NSKeyValueObservingOptionNew) {
value = [self valueForKeyPath:keyPath];
if (value == nil) {
value = [NSNull null];
}
}
//發(fā)送注冊(cè)通知
_NSKeyValueNotifyObserver(observer, keyPath, self, context, value,
0 /*originalObservable*/, 1 /*NSKeyValueChangeSetting*/);
//加鎖
pthread_mutex_lock(__NSKeyValueObserverRegistrationLock);
}
//獲取屬性的觀察信息
Info *info = __NSKeyValueRetainedObservationInfoForObject(self, property->_containerClass);
//判斷是否需要獲取新的數(shù)值
id _additionOriginalObservable = nil;
if (option & NSKeyValueObservingOptionNew) {
//0?x15沒有找到定義茂洒,猜測(cè)為保存是否可觀察的數(shù)組
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);
//屬性添加后遞歸添加關(guān)聯(lián)屬性
[property object:self didAddObservance:newInfo recurse:true];
//獲取新的isa
// 如果一個(gè)對(duì)象是第一次被觀察孟岛,需要替換isa對(duì)象
Class cls = [property isaForAutonotifying];
if ((cls != NULL) && (object_getClass(self) != cls)) {
//如果是第一次就替換isa
object_setClass(self, cls);
}
//釋放觀察信息
[newInfo release];
if (info != nil) {
[info release];
}
return;
}
給一個(gè)類動(dòng)態(tài)的添加method
// 給一個(gè)類添加方法
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);
}
如圖4所示,動(dòng)態(tài)添加了_isKVOA
三. GNU addObserver 實(shí)現(xiàn)
// 添加觀察者
- (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.
*/
// 獲取對(duì)象的觀察者信息
info = (GSKVOInfo*)[self observationInfo];
// 如果info 為空
if (info == nil)
{
//創(chuàng)建觀察者信息
info = [[GSKVOInfo alloc] initWithInstance: self];
// 保存觀察者信息
[self setObservationInfo: info];
// [r replacement] 就是子類,將self設(shè)置為子類類型(修改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"];
如圖6所示:當(dāng)移除KVO時(shí)渠羞,如果該對(duì)象沒有監(jiān)聽者斤贰,會(huì)銷毀NSKeyValueObservance
釋放對(duì)象
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 實(shí)現(xiàn)
/*
* removes the observer
移除監(jiān)聽者
*/
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
GSKVOPathInfo *pathInfo;
[iLock lock];
// 獲取觀察者信息
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
if (pathInfo != nil)
{
// 移除path對(duì)應(yīng)的所有觀察者
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);
打印結(jié)果-------NSKVONotifying_Person
說明移除KVO之后,NSKVONotifying_Person沒有被銷毀
NSKVODeallocate 實(shí)現(xiàn)
使用hopper查看蘋果實(shí)現(xiàn)
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實(shí)現(xiàn)
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對(duì)象已經(jīng)被銷毀了,但是觀察者沒有被移除屯吊。從上面的分析可以看到 NSKeyValueObservance就無(wú)法銷毀送巡。當(dāng)person對(duì)象銷毀時(shí),會(huì)根據(jù)isa指針找到-dealloc 方法雌芽,由于觀察者沒有移除授艰,isa指向 NSKVONotifying_Person類,這個(gè)類重寫了-dealloc方法世落,會(huì)做一些清除kvo的操作淮腾,所以需要重新子類的-dealloc 方法。
KVO源碼中添加觀察者時(shí)整體的大致流程是什么?
- 將keyPath屉佳、class等信息封裝成NSKeyValueProperty谷朝,分別解析一般屬性(@”aa”)、可計(jì)算屬性(@”@aa”)武花、屬性鏈(@”aa.bb.@cc.dd“)圆凰,進(jìn)行子類化,緩存在CFMutableSet中方便下次快速取出体箕。
- 將NSKeyValueProperty专钉、context、options累铅、observer等信息封裝成NSKeyValueObservance跃须,緩存在NSHashTable中。
- 倘若設(shè)置了NSKeyValueObservingOptionInitial選項(xiàng)娃兽,會(huì)在注冊(cè)觀察服務(wù)時(shí)調(diào)用一次觸發(fā)方法菇民。
- 動(dòng)態(tài)創(chuàng)建名為NSKVONotifying_+原來類名的新類,重寫其dealloc投储、_isKVOA方法第练,再重寫class方法,利用object_setClass()函數(shù)將其isa指針指向原先的類玛荞。
- 重寫willChangeValueForKey:和didChangeValueForKey:方法娇掏,重寫被觀察屬性的setter方法,在setter中先調(diào)用willChangeValueForKey:方法勋眯,然后調(diào)用父類的 setter 方法對(duì)成員變量賦值婴梧,之后再調(diào)用 didChangeValueForKey: 方法壁涎。
- didChangeValueForKey: 方法中會(huì)調(diào)用observeValueForKeyPath:ofObject:change:context:方法。