????對于有iOS開發(fā)經(jīng)驗的同學(xué)來說KVO不在陌生,但是有好多同學(xué)只是停留在會用的基礎(chǔ)上眉撵,但是對于其的深層原理還是不怎么了解侦香,接下來我們來深入的認(rèn)識一下KVO的本質(zhì)。
? ? KVO被我們經(jīng)常稱之為:鍵值監(jiān)聽纽疟,可以用于監(jiān)聽某個對象的屬性值的改變罐韩。接下來我們創(chuàng)建工程先實使用一下:
#import "ViewController.h"
@interface Person :NSObject
@property (assign, nonatomic)int age;
@end
@implementation Person
- (void)setAge:(int)age{? ? _age =age;}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
? ? [super viewDidLoad];? ?
????Person *person =[Person new];?
? ?person.age =10;? ?
[person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil];? ?
person.age =12;}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{?
? NSLog(@"keyPath :%@----change:%@",keyPath,change);
}
@end
創(chuàng)建了Person對象,一個age屬性污朽,將person對象被添加為controller監(jiān)聽age屬性的變化散吵,打印出來:
2018-04-21 22:33:44.652460+0800 kvo[5397:332335] keyPath :age----change:{? ?
kind = 1;?
new = 12;? ?
old = 10;
}
上邊就是一個KVO使用的全部過程,接下來我們分析:在person的age屬性被改變的時候其實是調(diào)用了它的set方法蟆肆,但是set方法里面又沒有額外的代碼執(zhí)行矾睦,那么它是什么時候?qū)ontroller對象進(jìn)行通知的呢?有基礎(chǔ)的同學(xué)可能就知道那么它肯定是在運(yùn)行時做了一些事情炎功,在runtime的時候?qū)erson對象做了一些東西才會被監(jiān)聽的枚冗。接下來我們對程序打兩個個斷點(diǎn)看:
那么看到這幅圖,相信了解OC對象實質(zhì)的同學(xué)都已經(jīng)看到了現(xiàn)在person對象的isa的指向在第一個斷點(diǎn)和第二個斷點(diǎn)發(fā)生了變化蛇损,而且person對象的地址也發(fā)生了變化赁温,我靠好神奇啊州藕!是不是覺得背后有一雙手在person對象添加鍵值監(jiān)聽的時候把的person對象給改變了變成了NSKVONotifying_Person類束世,答案是確實這樣!
來分析:在person為被加入監(jiān)聽的時候其實它的調(diào)用時這樣的:
而被添加監(jiān)聽之后其實是這樣的:
從上圖和我們的斷點(diǎn)中看出person對象在添加監(jiān)聽之后就衍生出了一個Person的子類NSKVONotifying_Person床玻,它的內(nèi)部信息如圖所示,所以調(diào)用賦值的過程其實是調(diào)用的NSKVONotifying_Person的set方法沉帮,它的內(nèi)部調(diào)用了一個C語言的私有函數(shù)_NSSstIntValueAndNotify()(中間int會根據(jù)你的屬性的類型做改變?nèi)纾篲NSSstCharValueAndNotify()...)這個函數(shù)锈死,但是這個函數(shù)其實內(nèi)部會調(diào)用:
只有當(dāng)will...和didChange...都調(diào)用的時候就會通知監(jiān)聽者,但是是在didChange...中通知的穆壕,而且如果在controller中直接實現(xiàn)這兩個方法待牵,在屬性值不改變的情況下也會出發(fā)監(jiān)聽回調(diào),可以作為手動出發(fā)監(jiān)聽的回調(diào)喇勋。
接下來我們驗證一下上邊的結(jié)論缨该,查看一下添加監(jiān)聽之后兩個對象的類對象的方法都有什么?
Person *p1 = [[Person alloc] init];? ? ?
p1.age = 1;? ? ?
Person *p2 = [[Person alloc] init];? ? ?
p2.age = 2;? //創(chuàng)建了兩個對象做對比? ?
// self監(jiān)聽p1的age屬性//? ?
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;//? ?
[p1 addObserver:self forKeyPath:@"age" options:options context:@"123"];? ?
[self printMethods:object_getClass(p2)];? ?
[self printMethods:object_getClass(p1)];
//該方法是mj老師寫的打印一個類對象里面的方法信息
- (void)printMethods:(Class)cls{? ?
unsigned int count;? ?
Method *methods = class_copyMethodList(cls, &count);?? ?? ?
NSMutableString *methodNames = [NSMutableString string];? ?
[methodNames appendFormat:@"%@ - ", cls];?? ?? ?
for (int i = 0; i < count; i++) {? ? ? ?
Method method = methods[i];?? ? ? ?? ? ? ?
NSString *methodName = NSStringFromSelector(method_getName(method));? ? ? ? [methodNames appendString:methodName];? ? ? ?
[methodNames appendString:@" "];? ? }?? ?? ?
NSLog(@"%@", methodNames);?? ?? ?
free(methods);//C語言的對象需要釋放
}
打印的結(jié)果如下:
Person - setAge: age
NSKVONotifying_Person - setAge: class dealloc _isKVOA
由此可見這是兩個類對象的信息川背,所以被添加監(jiān)聽的person對象調(diào)用set方法時候是調(diào)用的NSKVONotifying_Person里面的方法的贰拿,還有個驗證如圖:
由圖看見新建的p1和p2對象蛤袒,p1被添加了KVO監(jiān)聽前后所使用的set方法地址發(fā)生了改變,而p2沒被添加監(jiān)聽所以沒有發(fā)生改變膨更,由此說明p1被添加前后的set調(diào)用確實發(fā)了改變妙真。然后我們程序上打上斷點(diǎn)通過LLDB指令看一下到底發(fā)生了什么改變,如圖:
注意:Interview05-KVO是建立的項目名字荚守,是當(dāng)前項目的可執(zhí)行文件珍德,圖是借用的mj老師的
我們可以看出,p1沒被添加監(jiān)聽之前的set是執(zhí)行的目前建立的項目的可執(zhí)行文件矗漾,而被添加之后是執(zhí)行的Foundation的這個可執(zhí)行文件锈候,而且執(zhí)行的是:_NSSetIntValueAndNotify這個函數(shù),所以證明了之前的結(jié)論敞贡,另外還可以通過反編譯oc的Foundation庫查看一下晴及,找下這個函數(shù)不再演示。
最后還有個小說明:為什么person對象被添加監(jiān)聽之后雖然類型改變了但是通過
[person class]嫡锌;
這個方法獲取的類還是Person呢虑稼,這個其實apple做了個掩護(hù),不想讓開發(fā)者知道內(nèi)部做了些什么势木。剩下的咱們可以猜一下蛛倦,其實在NSKOVNotifying_Person這個類中的class的get方法中做了如下操作:
- (Class)class{
????return class_getSuperclass(object_getClass(self));
}
所以person對象在我們添加了監(jiān)聽之后返回的class還是Person這個類,一切都是假象啦啦桌!哈哈
還有在文章中涉及到OC對象本質(zhì)的東西溯壶,可以看我的另外一篇文章:OC的對象本質(zhì)
ps:這個文章其實是我的學(xué)習(xí)筆記了,在小碼哥學(xué)習(xí)李明杰老師的iOS底層網(wǎng)絡(luò)班授課做得筆記甫男,所以用了好多mj老師的東西包括老師的思路和證明方法且改,感謝mj老師在it界的耕耘!