KVC是怎么訪問屬性的
- KVC在某種程度上提供了替代存取方法(訪問器方法)的方案滤灯,不過存取方法終究是個(gè)好東西,以至于只要有可能曼玩,KVC也盡可能先嘗試使用存取方法訪問屬性鳞骤。當(dāng)使用KVC訪問屬性時(shí),它內(nèi)部其實(shí)做了很多事:
- 首先查找有無<property>黍判,set<property>豫尽,is<property>等property屬性對應(yīng)的存取方法,若有顷帖,則直接使用這些方法;
- 若無美旧,則繼續(xù)查找<property>,_get<property>贬墩,_set<property>等方法榴嗅,若有就使用;
- 若查詢不到以上任何存取方法陶舞,則嘗試直接訪問實(shí)例變量<property>嗽测,<property>;
- 若連該成員變量也訪問不到,則會在下面方法中拋出異常唠粥。之所以提供這兩個(gè)方法疏魏,valueForUndefinedKey:和setValue:forUndefinedKey:方法,,就是讓你在因訪問不到該屬性而程序即將崩掉前,供你重寫晤愧,在內(nèi)做些處理大莫,防止程序直接崩掉。
KVO是什么官份?
Key-Value Obersver只厘,即鍵值觀察。它是觀察者模式的一種衍生舅巷「嵛叮基本思想是,對目標(biāo)對象的某屬性添加觀察悄谐,當(dāng)該屬性發(fā)生變化時(shí)介评,會自動(dòng)的通知觀察者库北。這里所謂的通知是觸發(fā)觀察者對象實(shí)現(xiàn)的KVO的接口方法爬舰。KVO是解決model和view同步的好法子。另外寒瓦,KVO的優(yōu)點(diǎn)是當(dāng)被觀察的屬性值改變時(shí)是會自動(dòng)發(fā)送通知的情屹,這比通知中心需要post通知來說,簡單了許多杂腰。
首先給目標(biāo)對象的屬性添加觀察:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- 實(shí)現(xiàn)下面方法來接收通知垃你,需要注意各個(gè)參數(shù)的含義:
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
- 最后要移除觀察者:
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
KVO是怎么實(shí)現(xiàn)的?
當(dāng)某個(gè)類的對象第一次被觀察時(shí)喂很,系統(tǒng)就會在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類惜颇,在這個(gè)派生類中重寫基類中被觀察屬性的 setter 方法,在setter方法里使其具有通知機(jī)制少辣。因此凌摄,要想KVO生效,必須直接或間接的通過setter方法訪問屬性(KVC的setValue就是間接方式)漓帅。直接訪問成員變量KVO是不生效的锨亏。
同時(shí)派生類還重寫了 class 方法以“欺騙”外部調(diào)用者它就是起初的那個(gè)類。然后系統(tǒng)將這個(gè)對象的 isa 指針指向這個(gè)新誕生的派生類忙干,因此這個(gè)對象就成為該派生類的對象了器予,因而在該對象上對 setter 的調(diào)用就會調(diào)用重寫的 setter,從而激活鍵值通知機(jī)制捐迫。此外乾翔,派生類還重寫了 dealloc 方法來釋放資源。
重新的setter方法里到底干了什么施戴,而使其就有了通知機(jī)制呢末融?其實(shí)只是在setter方法里钧惧,給屬性賦值的前后分別調(diào)用了兩個(gè)方法
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
而- (void)didChangeValueForKey:(NSString *)key;會調(diào)用
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
這就是KVO實(shí)現(xiàn)的基本原理了!
當(dāng)沒有存取方法而通過KVC的setValue修改屬性值時(shí)勾习,同樣的在運(yùn)行時(shí)也會在setValue:forKey方法里默認(rèn)調(diào)用上面?zhèn)z方法浓瞪。
其實(shí)我們也可以手動(dòng),顯式的調(diào)用這兩個(gè)方法巧婶,以使其具有通知機(jī)制乾颁。
下面用例子驗(yàn)證:
#import "ViewController.h"
@interface ViewController ()
{
NSString *_testStr;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 給self的添加self觀察者,自己觀察自己的testStr成員變量
[self addObserver:self forKeyPath:@"testStr" options:NSKeyValueObservingOptionNew context:nil];
[self willChangeValueForKey:@"testStr"];
_testStr = @"this is a test"; // 直接修改成員變量的值艺栈,但是顯式的英岭、手動(dòng)的調(diào)用上下倆方法,使其就有通知機(jī)制
[self didChangeValueForKey:@"testStr"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if(object == self && [keyPath isEqualToString:@"testStr"])
{
NSLog(@"----new:%@----",change[@"new"]);
}else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc
{
// 移除觀察者
[self removeObserver:self forKeyPath:@"stuName"];
}
@end