寫在前面:
??關于KVC和KVO各種博客多了去了,重新整理下,就當是溫習一下吧,不對的地方請指教,喜歡的點個喜歡什么也是挺好。
一,KVC
KVC也就是key-value-coding,即鍵值編碼,通常是用來給某一個對象的屬性進行賦值,例如有人這么一個類,其對外有兩個屬性,姓名和年齡,我們在創建了一個人p后可以通過點語法直接給p賦值。
Person *p = [[Person alloc] init];
p.name = @"張三";
p.age = 20;
我們也可以通過kvc給這個人p賦值,代碼如下,因為setValue這里的值是id類型的,所以將整數包裝成一個對象,
[p setValue:@"張三" forKey:@"name"];
[p setValue:@20 forKey:@"age"];
但是我們這樣去賦值顯得多此一舉,可是如果人這個類的屬性是沒有暴露在外面呢?比如現在給人這個類一個私有的身高的屬性,并且對外提供一個輸出身高的接口,如下
#import "Person.h"
@implementation Person
{
NSInteger _height;
}
- (void)logHeight
{
NSLog(@"%ld",_height);
}
@end
這時候我們是沒有辦法去給人p直接設置身高的,外面我們訪問不到它.但是有了kvc就不一樣了。
[p setValue:@170 forKey:@"height"];
我們通過kvc可以直接對私有屬性進行賦值,打印如下
除了[p setValue:@170 forKey:@"height"]
這個方法外,還有一個方法也是可以對私有屬性進行賦值的[p setValue:@170 forKeyPath:@"height"];
這兩個方法對于一個普通的屬性是沒有區別的,都可以用,但是對于一些特殊的屬性就有區別了。
比如說人這個類有個屬性是狗,狗又有屬性體重。
p.dog = [[Dog alloc] init];
[p setValue:@200 forKey:@"dog.weight"];
如果我們直接這樣是會報錯說找不到dog.weight這個key的,而在storyboard中,我們拖控件連線錯誤的時候也會報錯說找不到什么key,說明storyboard在賦值的時候也是通過kvc來操作的。
這里如果我們換另外的一個方法,這時候是不會報錯的,而且可以打印出狗的體重.
[p setValue:@200 forKeyPath:@"dog.weight"];
說明forKeyPath
是包含了forKey
這個方法的功能的,甚至forKeyPath
方法還有它自己的高級的功能,它會先去找有沒有dog
這個key
,然后去找有沒有weight
這個屬性。所以我們在使用kvc的時候,最好用forKeyPath
這個方法。
最后還有一點,如下代碼
[p setValue:@170 forKey:@"height"];
我們傳入的字符串key是height
,但是定義的屬性是_height
,但是通過kvc還是可以給_height
屬性賦到值。說明對某一個屬性進行賦值,可以不用加下劃線,而且它的查找規則應該是:先查找和直接寫入的字符串相同的成員變量,如果找不到就找以下劃線開頭的成員變量。
??kvc除了訪問私有變量這個用處外,還可以用于字典轉模型。在Person
類對外提供一個接口,將轉模型的工作放在模型中進行
- (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
returnself;
}
外面可以直接將字典傳入,和平常轉模型相比,kvc更加方便,減少了代碼量。
NSDictionary*PersonDict = @{@"name":@"李四",@"age":@"18"};
Person *p2 = [Person personWithDict:PersonDict];
NSLog(@"name = %@,age =%ld",p2.name,p2.age);
所以kvc最常見的兩種用法就是:
1,對私有變量進行賦值
2,字典轉模型
但是也有一些需要注意的地方
1,字典轉模型的時候,字典中的某一個key一定要在模型中有對應的屬性
2,如果一個模型中包含了另外的模型對象,是不能直接轉化成功的。
3,通過kvc轉化模型中的模型,也是不能直接轉化成功的。
既然可以通過kvc賦值,同樣的也可以通過它進行取值。
NSLog(@"name=%@",[p2 valueForKey:@"name"]);
NSLog(@"dogweight=%@", [p2 valueForKeyPath:@"dog.weight"]);
二,KVO
KVO,即key-value-observing,利用一個key來找到某個屬性并監聽其值得改變。其實這也是一種典型的觀察者模式。
簡單的說,kvo的用法非常簡單。
1,添加觀察者
2,在觀察者中實現監聽方法,observeValueForKeyPath: ofObject: change: context:
(通過查閱文檔可以知道,絕大多數對象都有這個方法,因為這個方法屬于NSObject)
3,移除觀察者
具體代碼如下:
//讓對象b監聽對象a的name屬性
//options屬性可以選擇是哪個
/*
NSKeyValueObservingOptionNew =
0x01, 新值
NSKeyValueObservingOptionOld =
0x02, 舊值
*/
[a addObserver:b forKeyPath:@"name"options:kNilOptionscontext:nil];
a.name = @"zzz";
#pragma mark - 實現KVO回調方法
/*
* 當對象的屬性發生改變會調用該方法
* @param keyPath 監聽的屬性
* @param object 監聽的對象
* @param change 新值和舊值
* @param context 額外的數據
*/
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id>
*)change context:(void *)context
{
NSLog(@"%@的值改變了,",keyPath);
NSLog(@"change:%@", change);
}
最后不要忘記,和通知一樣,要在dealloc方法里面移除監聽
- (void)dealloc
{
[a removeObserver:b forKeyPath:@"name"];
}
KVO的底層實現
當一個類的屬性被觀察的時候,系統會通過runtime動態的創建一個該類的派生類,并且會在這個類中重寫基類被觀察的屬性的setter方法,而且系統將這個類的isa指針指向了派生類,從而實現了給監聽的屬性賦值時調用的是派生類的setter方法。重寫的setter方法會在調用原setter方法前后,通知觀察對象值得改變。
??具體實現圖如下,這里我拿的是iOS程序猿的圖,借用一下應該沒關系吧?
最后
??貌似有個facebook開源的工具,KVOController ,是一個簡單安全的 KVO(Key-value Observing,鍵-值 觀察)工具,好像挺好用的。