最近打算從新梳理一遍iOS底層的知識,儘可能把全部的底層知識點都搞懂搞透徹,礙於iOS不開源,有不少東西並不能很直觀的去學習,因此可能有瑕疵,但願你們能夠理解,並一塊兒交流,筆者也儘量作到盡善盡美吧。html
KVO的底層是如何實現的呢?api
對於這個問題,我想你們均可以簡單的聊上這麼幾句。框架
對某個實例的某一個屬性添加KVO監聽後,系統會利用runtime的運行時特性,生成一個臨時的類NSKVONotifying_xxx,而後把該實例的isa指針指向NSKVONotifying_xxx,監聽哪一個屬性,就重寫NSKVONotifying_xxx中此屬性的set方法,而後在重寫的set方法中實現監聽和通知。
簡單的來講就是這樣,可是這太籠統了,下面咱們經過例子,一步一步的來分析。學習
Person * person1 = [[Person alloc] init]; Person * person2 = [[Person alloc] init]; [person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; person1.age = 10; person2.age = 20;
這是一個最基本的KVO使用,在回調中只有person1的值改變被監聽了,可是咱們在賦值的時候都是調用了age的set方法,若是咱們在Person類中實現setAge:的方法並debug,這兩次賦值都會走setAge方法,問題不是出在setAge這裏,因此咱們推測多是類發聲了某些變化。(此處應該有runtime的知識基礎,runtime有能力對類作一些動態的改變)。this
因此咱們能夠獲取一下這兩個實例的類型atom
// 輸出 person1:NSKVONotifying_Person NSLog(@"person1:%@",object_getClass(person1)); // 輸出 person2:Person NSLog(@"person2:%@",object_getClass(person2));
到這裏咱們就能夠肯定確實是生成了一箇中間類。而且讓person1的isa指針指向了這個類(object_getClassName方法就是返回isa的指向)。spa
注:此處爲何要用runtime的api,由於runtime的api調用後的結果更加接近本質debug
首先咱們先看一下這面這個圖,其實這就是添加了KVO以後類的類型結構指針
關於NSKVONotifying_Person類實現的方法,咱們是怎麼樣獲得的呢,這裏咱們能夠藉助runtime的api窺探一下。code
[self printMethodList:object_getClass(person1)]; // 下面是方法實現 - (void)printMethodList:(Class)cls { unsigned int count; Method * methodList = class_copyMethodList(cls, &count); for (unsigned int i = 0; i < count; i++) { Method method = methodList[i]; NSLog(@"method(%d) : %@", i, NSStringFromSelector(method_getName(method))); } free(methodList); }
輸出結果
method(0) : setAge: method(1) : class method(2) : dealloc method(3) : _isKVOA
到這一步,咱們能夠先作一下小總結:
person2的isa指針指向Person類,因此在setAge的時候,就直接調用了Person中實現的setAge:方法,正常的賦值操做,沒有觸發KVO。可是person1的isa動態改變,指向了NSKVONotifying_Person,同時NSKVONotifying_Person中又從新實現了setAge:方法,因此在給person1的age賦值時,首先調用的是NSKVONotifying_Person中的setAge:方法,可是咱們在以前的debug中發現,Person中的setAge:方法也會調用,其實這很容易理解,這應該是在NSKVONotifying_Person的setAge:實現中又調用了Person的setAge,畢竟NSKVONotifying_Person的isa指向Person(請自行驗證)。
我的感受,挖掘setAge:的實現是比較難的。
咱們經過下面的方法打印一下方法IMP的地址
NSLog(@"person1添加KVO以前的兩個setAge地址: \n -person1:%p -- person2:%p", [person1 methodForSelector:@selector(setAge:)], [person2 methodForSelector:@selector(setAge:)]); [person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; NSLog(@"person1添加KVO以後的兩個setAge地址: \n -person1:%p -- person2:%p", [person1 methodForSelector:@selector(setAge:)], [person2 methodForSelector:@selector(setAge:)]);
輸出:
person1添加KVO以前的兩個setAge地址: -person1:0x1005ee850 -- person2:0x1005ee850 person1添加KVO以後的兩個setAge地址: -person1:0x7fff25623f0e -- person2:0x1005ee850
咱們能夠看到,在添加KVO監聽先後,person2的setAge實現的地址沒有發生變化,可是person1的變了,咱們在打印臺用lldb命令打印一下0x7fff25623f0e
(lldb) p (IMP)0x7fff25623f0e (IMP) $1 = 0x00007fff25623f0e (Foundation`_NSSetIntValueAndNotify)
能夠看到setAge的實現其實就是調用了Foundation框架的_NSSetIntValueAndNotify方法。那具體的_NSSetIntValueAndNotify內部實現是怎麼樣的呢?由於Foundation不開源,咱們只能猜想,並對咱們的猜想作出相應的驗證。
下面是我看過一些大神的分析以後猜想的_NSSetIntValueAndNotify實現的僞代碼(特此鳴謝咱們的MJ老師)
void _NSSetIntValueAndNotify() { [self willChangeValueForKey:@"age"]; [super setAge:age]; [self didChangeValueForKey:@"age"]; } - (void)didChangeValueForKey:(NSString *)keyPath { // 通知監聽者,已經修改完畢 [observer observeValueForKeyPath:keyPath ofObject:self change:nil context:nil]; }
如何驗證一下咱們的猜想呢?
咱們知道NSKVONotifying_Person類中沒有實現willChangeValueForKey和didChangeValueForKey這兩個方法,因此咱們能夠在NSKVONotifying_Person的父類,也就是Person類型重寫這兩個方法,改造完以後的Person類裏面應該是下面這樣子:
@interface Person : NSObject @property (nonatomic, assign) int age; @end @implementation Person - (void)setAge:(int)age { _age = age; NSLog(@"age:%d",age); } - (void)willChangeValueForKey:(NSString *)key { [super willChangeValueForKey:key]; NSLog(@"willChangeValueForKey"); } - (void)didChangeValueForKey:(NSString *)key { NSLog(@"didChangeValueForKey - begin"); [super didChangeValueForKey:key]; NSLog(@"didChangeValueForKey - end"); } @end
person1在添加了KVO監聽,並設置值person1.age = 10;
以後,輸出以下:
2020-09-07 23:47:13.787066+0800 KVO[8122:119707] willChangeValueForKey 2020-09-07 23:47:13.787229+0800 KVO[8122:119707] age:10 2020-09-07 23:47:13.787334+0800 KVO[8122:119707] didChangeValueForKey - begin 2020-09-07 23:47:13.787592+0800 KVO[8122:119707] <Person: 0x60000288c190> -- age -- { kind = 1; new = 10; old = 0; } 2020-09-07 23:47:13.787732+0800 KVO[8122:119707] didChangeValueForKey - end
輸出結果與咱們的猜想一致。
NSKVONotifying_Person也重寫了class方法,使用[person1 class]的時候返回的是Person,其實也很容易理解,只是爲了隱藏NSKVONotifying_Person這個類,儘可能隱藏KVO的內部實現。
你們也能夠看一下我下面附上的參考文章,寫的很不錯。
通過上面的層層分析,咱們探究了KVO的實現原理,有不縝密的地方還請指點。
感謝閱讀。