關(guān)于KVC
KVC是什么?
Key-Value Coding,即鍵值編碼。它是一種不通過存取方法氯夷,而通過屬性名稱字符串間接訪問屬性的機(jī)制。
KVC常用的方法
前兩個方法無論獲取值還是賦值靶擦,只需要傳入屬性名稱的字符串就行了腮考。但KVC也提供了傳入path
的方法。所謂path玄捕,就是用點(diǎn)號連接的多層級的屬性踩蔚,比如student.name
,student屬性里的name屬性枚粘。
- (id)valueForKey:(NSString *)key;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
KVC對多種數(shù)據(jù)類型的支持
首先要說的是對于基本數(shù)據(jù)類型的屬性馅闽,KVC的這幾個方法會自動裝箱和拆箱。
其次馍迄,KVC也支持?jǐn)?shù)組和字典等集合數(shù)據(jù)福也。這里不多探討,關(guān)于這些知識可以看這篇博文:KVC/KVO原理詳解及編程指南
KVC的原理:KVC是怎么訪問屬性的攀圈?
KVC在某種程度上提供了替代存取方法(訪問器方法)的方案暴凑,不過存取方法終究是個好東西,以至于只要有可能赘来,KVC也盡可能先嘗試使用存取方法訪問屬性现喳。當(dāng)使用KVC訪問屬性時,它內(nèi)部其實(shí)做了很多事:
1.首先查找有無<property>撕捍,set<property>拿穴,is<property>等property屬性對應(yīng)的存取方法泣洞,若有忧风,則直接使用這些方法;
2.若無,則繼續(xù)查找_<property>球凰,_get<property>狮腿,set<property>等方法腿宰,若有就使用;
3.若查詢不到以上任何存取方法缘厢,則嘗試直接訪問實(shí)例變量<property>吃度,<property>;
4.若連該成員變量也訪問不到贴硫,則會在下面方法中拋出異常椿每。之所以提供這兩個方法,就是讓你在因訪問不到該屬性而程序即將崩掉前英遭,供你重寫间护,在內(nèi)做些處理,防止程序直接崩掉挖诸。
valueForUndefinedKey:
和setValue:forUndefinedKey:
方法汁尺。
關(guān)于KVO
KVO是什么?
Key-Value Obersver多律,即鍵值觀察痴突。它是觀察者模式的一種衍生±擒瘢基本思想是辽装,對目標(biāo)對象的某屬性添加觀察,當(dāng)該屬性發(fā)生變化時粘秆,會自動的通知觀察者如迟。這里所謂的通知是觸發(fā)觀察者對象實(shí)現(xiàn)的KVO的接口方法。
** KVO是解決model和view同步的好法子攻走。**
另外殷勘,KVO的優(yōu)點(diǎn)是當(dāng)被觀察的屬性值改變時是會自動發(fā)送通知的,這比通知中心需要post通知來說昔搂,簡單了許多玲销。
KVO怎么用?
1.首先給目標(biāo)對象的屬性添加觀察:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
2.實(shí)現(xiàn)下面方法來接收通知摘符,需要注意各個參數(shù)的含義:
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
3.最后要移除觀察者:
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
舉一個??:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
{
Student *_student;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_student = [[Student alloc] init];
_student.stuName = @"oldName_hu";
// 1.給student對象的添加觀察者贤斜,觀察其stuName屬性
[_student addObserver:self forKeyPath:@"stuName" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
// 此時,stuName發(fā)生了變化
_student.stuName = @"newName_wang";
}
// stuName發(fā)生變化后逛裤,觀察者(self)立馬得到通知瘩绒。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
// 最好判斷目標(biāo)對象object和屬性路徑keyPath
if(object == _student && [keyPath isEqualToString:@"stuName"])
{
NSLog(@"----old:%@----new:%@",change[@"old"],change[@"new"]);
}else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc
{
// 移除觀察者
[_student removeObserver:self forKeyPath:@"stuName"];
}
@end
上面代碼需要注意的一點(diǎn)是接收通知的方法里最好判斷一下目標(biāo)對象和屬性路徑,因?yàn)橛锌赡苡卸鄠€KVO觀察多個對象的屬性带族;而且锁荔,父類也有可能使用了KVO哦,所以在else里蝙砌,對現(xiàn)有條件外的情況交給父類去處理阳堕。
KVO的原理:KVO是怎么實(shí)現(xiàn)的跋理?
當(dāng)某個類的對象第一次被觀察時,系統(tǒng)就會在運(yùn)行期動態(tài)地創(chuàng)建該類的一個派生類恬总,在這個派生類中重寫基類中被觀察屬性的 setter 方法前普,在setter方法里使其具有通知機(jī)制。因此壹堰,要想KVO生效拭卿,必須直接或間接的通過setter方法訪問屬性(KVC的setValue就是間接方式)。直接訪問成員變量KVO是不生效的贱纠。
同時派生類還重寫了 class 方法以“欺騙”外部調(diào)用者它就是起初的那個類记劈。然后系統(tǒng)將這個對象的 isa 指針指向這個新誕生的派生類,因此這個對象就成為該派生類的對象了并巍,因而在該對象上對 setter 的調(diào)用就會調(diào)用重寫的 setter目木,從而激活鍵值通知機(jī)制。此外懊渡,派生類還重寫了 dealloc 方法來釋放資源刽射。
重新的setter方法里到底干了什么,而使其就有了通知機(jī)制呢剃执?其實(shí)只是在setter方法里誓禁,給屬性賦值的前后分別調(diào)用了兩個方法
- (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修改屬性值時肾档,同樣的在運(yùn)行時也會在setValue:forKey方法里默認(rèn)調(diào)用上面?zhèn)z方法摹恰。
** 其實(shí)我們也可以手動,顯式的調(diào)用這兩個方法怒见,以使其具有通知機(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"; // 直接修改成員變量的值遣耍,但是顯式的闺阱、手動的調(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