KVO
基本使用
給 person 對象添加KVO監(jiān)聽
Person *person = [[Person alloc] init];
person.age = 10;
self.person = person;
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
當監(jiān)聽對象的屬性值發(fā)生改變時豪诲,就會調用顶捷。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"屬性值更改了 %@", change);
}
移除監(jiān)聽
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"age"];
}
本質分析
Person *person1 = [[Person alloc] init];
person1.age = 1;
self.person1 = person1;
Person *person2 = [[Person alloc] init];
person2.age = 2;
self.person2 = person2;
[self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
當 person1
使用KVO監(jiān)聽后,person1
的 isa
會指向一個全新的類 NSKVONotifying_Person
屎篱,NSKVONotifying_Person
是使用 Runtime
動態(tài)創(chuàng)建的一個 Person
類的子類服赎,即 NSKVONotifying_Person
的 superclass
指向的是 Person
類對象。
- 如果對實例對象
person2
的age
進行賦值交播,person2
會通過 isa 指針找到類Person
重虑,然后找到方法列表中的setAge
進行調用。 - 如果對實例對象
person1
的age
進行賦值秦士,person1
會通過 isa 指針找到類NSKVONotifying_Person
缺厉,然后找到方法列表中的setAge
進行調用。但是NSKVONotifying_Person
中的setAge
方法內大致的邏輯是:執(zhí)行Foundation
框架中的_NSSetIntValueAndNotify
函數隧土,而_NSSetIntValueAndNotify
函數內部會陸續(xù)調用willChangeValueForKey
提针、[super setAge:age]
(原來的setter實現(xiàn))、didChangeValueForKey
等方法次洼,didChangeValueForKey
方法會執(zhí)行observeValueForKeyPath: ofObject: change: context:
通知監(jiān)聽器关贵,最終執(zhí)行代理方法observeValueForKeyPath: ofObject: change: context:
。
因為 age
為 int
類型卖毁,所以是 _NSSetIntValueAndNotify
揖曾。如果屬性 age
是其它類型,相對應的還有 _NSSetBoolValueAndNotify
亥啦、_NSSetCharValueAndNotify
炭剪、_NSSetObjectValueAndNotify
、_NSSetFloatValueAndNotify
、_NSSetSizeValueAndNotify
炉奴、 等畦攘。
NSKVONotifying_Person
類中重寫了 - (Class)class
方法,使得 [self.person1 class]
為 Person
错妖,而通過運行時 object_getClass(self.person1)
獲取時是 NSKVONotifying_Person
绿鸣。
獲取 NSKVONotifying_Person
類中的方法:
Class class = object_getClass(self.person1);
NSMutableArray *methodNames = [NSMutableArray array];
unsigned int count;
Method *methodList = class_copyMethodList(class, &count);
for (NSInteger i = 0; i < count; i ++) {
Method method = methodList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames addObject:methodName];
}
free(methodList);
/*
(
"setAge:",
class,
dealloc,
"_isKVOA"
)
*/
NSLog(@"%@", methodNames);
驗證
NSLog(@"person1 添加KVO監(jiān)聽之前 類對象(self.person1.isa): %@ 元類對象(self.person1.isa.isa): %@ 方法內存地址: %p", object_getClass(self.person1), object_getClass(object_getClass(self.person1)), [self.person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person2 添加KVO監(jiān)聽之前 類對象(self.person2.isa): %@ 元類對象(self.person2.isa.isa): %@ 方法內存地址: %p", object_getClass(self.person2), object_getClass(object_getClass(self.person2)), [self.person2 methodForSelector:@selector(setAge:)]);
[self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
NSLog(@"person1 添加KVO監(jiān)聽之后 類對象(self.person1.isa): %@ 元類對象(self.person1.isa.isa): %@ 方法內存地址: %p", object_getClass(self.person1), object_getClass(object_getClass(self.person1)), [self.person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person2 添加KVO監(jiān)聽之后 類對象(self.person2.isa): %@ 元類對象(self.person2.isa.isa): %@ 方法內存地址: %p", object_getClass(self.person2), object_getClass(object_getClass(self.person2)), [self.person2 methodForSelector:@selector(setAge:)]);
所以 person1
對象的 setAge:
具體實現(xiàn)是 _NSSetIntValueAndNotify
iOS用什么方式實現(xiàn)對一個對象的KVO?(KVO的本質是什么暂氯?)
- 利用 RuntimeAPI 動態(tài)生成一個子類潮模,并且讓instance對象的isa指向這個全新的子類
- 當修改instance對象的屬性時,會調用
Foundation
的_NSSetxxxValueAndNotify
函數
--willChangeValueForKey:
-- 父類原來的setter
--didChangeValueForKey:
內部會觸發(fā)監(jiān)聽器(Oberser)的監(jiān)聽方法(observeValueForKeyPath:ofObject:change:context:
)
手動調用 willChangeValueForKey:
和 didChangeValueForKey:
可以手動觸發(fā)KVO
直接修改成員變量不會觸發(fā)KVO
KVC
KVC的全稱是Key-Value Coding痴施,俗稱“鍵值編碼”擎厢,可以通過一個key來訪問某個屬性。
常見的API有:
(void)setValue:(id)value forKeyPath:(NSString *)keyPath;
(void)setValue:(id)value forKey:(NSString *)key;
(id)valueForKeyPath:(NSString *)keyPath;
(id)valueForKey:(NSString *)key;
setValue:forKey: 的原理
首先按照
setKey:
辣吃、_setKey:
的順序查找方法动遭。如果找到了方法就傳遞參數,調用方法神得。如果沒找到厘惦,則查看
accessInstanceVariablesDirectly
方法 的返回值(默認值是 YES),如果返回 NO循头,調用setValue: forUndefinedKey:
并拋出異常NSUnknownKeyException
绵估。如果
accessInstanceVariablesDirectly
返回 YES,按照_key
卡骂、_isKey
国裳、key
、isKey
的順序查找成員變量全跨,找到了成員變量直接賦值缝左。如果沒找到成員變量,調用
setValue: forUndefinedKey:
并拋出異常NSUnknownKeyException
浓若。
valueForKey: 的原理
首先按照
getKey
渺杉、key
、isKey
挪钓、_key
的順序查找方法是越,找到了就調用方法。如果沒找到碌上,則查看
accessInstanceVariablesDirectly
方法 的返回值(默認值是 YES)倚评,如果返回 NO,調用valueForUndefinedKey:
并拋出異常NSUnknownKeyException
馏予。如果
accessInstanceVariablesDirectly
返回 YES天梧,按照_key
、_isKey
霞丧、key
呢岗、isKey
的順序查找成員變量,找到了成員變量直接取值。如果沒找到成員變量后豫,調用
valueForUndefinedKey:
并拋出異常NSUnknownKeyException
悉尾。