KVC和KVO在實(shí)際的運(yùn)用中是很常見(jiàn)的。所以了解它的底層實(shí)現(xiàn)原理是非常不錯(cuò)的一件事。
KVC(NSKeyValueCoding)
KVC就是通過(guò)key值,來(lái)獲取對(duì)象的屬性進(jìn)行操作,而不是通過(guò)我們明確的存取方式來(lái)獲取,是一個(gè)非正式的Protocol。KVO就是基于KVC來(lái)實(shí)現(xiàn)的。
KVC的一般使用:
@interface Person : NSObject
{
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
- (void)testName;
@end
@implementation Person
- (NSString *)getName {
NSLog(@"%s",__func__);
return @"D";
}
- (NSString *)name {
NSLog(@"%s",__func__);
return @"D";
}
- (NSString *)isName {
NSLog(@"%s",__func__);
return @"D";
}
- (void)setName:(NSString *)name {
NSLog(@"%s",__func__);
}
//- (NSInteger)countOfName {
// return 2;
//}
//
//- (id)objectInNameAtIndex:(NSInteger)index {
// return @"arrayItem";
//}
- (void)testName {
NSLog(@"_name = %@",_name);
NSLog(@"name = %@",name);
NSLog(@"isName = %@",isName);
NSLog(@"_isName = %@",_isName);
}
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"取值沒(méi)有找到這個(gè)key %@",key);
return nil;
}
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key {
NSLog(@"設(shè)值沒(méi)有找到這個(gè)key %@",key);
}
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
[person valueForKey:@"name"];
[person setValue:@"ADA" forKey:@"name"];
[person testName];
}
運(yùn)行后,set和get方法都會(huì)被執(zhí)行,但是這與點(diǎn)語(yǔ)法還是有區(qū)別的。
KVC有自己的執(zhí)行機(jī)制
在調(diào)用 setValue: forKey: 的時(shí),程序優(yōu)先調(diào)用 setName: 方法,如果沒(méi)有找到 setName: 方法 KVC會(huì)檢查這個(gè)類的 + (BOOL)accessInstanceVariablesDirectly 類方法看是否返回YES(默認(rèn)YES),返回YES則會(huì)繼續(xù)查找該類有沒(méi)有名為_(kāi)name的成員變量,如果還是沒(méi)有找到則會(huì)繼續(xù)查找_isName成員變量,還是沒(méi)有則依次查找name,isName。上述的成員變量都沒(méi)找到則執(zhí)行setValue:forUndefinedKey: 拋出異常,如果不想程序崩潰應(yīng)該重寫該方法。假如這個(gè)類重寫了+ (BOOL)accessInstanceVariablesDirectly 返回的是NO,則程序沒(méi)有找到setName:方法之后,會(huì)直接執(zhí)行setValue:forUndefinedKey: 拋出異常。
在調(diào)用valueForKey:的時(shí),會(huì)依次按照getName,name,isName的順序進(jìn)行調(diào)用。如果這3個(gè)方法沒(méi)有找到,那么KVC會(huì)按照countOfName,objectInNameAtIndex來(lái)查找。如果查找到這兩個(gè)方法就會(huì)返回一個(gè)數(shù)組。如果還沒(méi)找到則調(diào)用+ (BOOL)accessInstanceVariablesDirectly 看是否返回YES,返回YES則依次按照_name,_isName,name,isName順序查找成員變量名,還是沒(méi)找到就調(diào)用valueForUndefinedKey:;返回NO直接調(diào)用valueForUndefinedKey:
KVC的一些注意
KVC在設(shè)置時(shí)可能會(huì)設(shè)置錯(cuò)誤的Key值導(dǎo)致程序崩潰,需要重寫valueForUndefinedKey:和setValue:forUndefinedKey:。還有一種是在設(shè)置中不小心傳遞了nil,這時(shí)候需要重寫setNilValueForKey:。
可能還有一些內(nèi)容我沒(méi)有提到,讀者可自行注釋上面所展示的代碼來(lái)驗(yàn)證查找的順序,會(huì)比較好理解。
KVO(Key-Value Observing)
KVO是OC設(shè)計(jì)模式中的一種,簡(jiǎn)單的說(shuō)就是添加一個(gè)被觀察對(duì)象A的屬性,當(dāng)被觀察對(duì)象A的屬性發(fā)生更改時(shí),觀察對(duì)象會(huì)獲得通知,并作出相應(yīng)的處理。NSObject類都實(shí)現(xiàn)了KVO ,解決了觀察對(duì)象和被觀察對(duì)象的解耦。
KVO的一般使用
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
@interface ViewController ()
{
Person *person;
}
@end
@implementation Person
//+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
// return NO;
//}
@end
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
person = [[Person alloc] init];
// 第三個(gè)參數(shù)代表新值
[person addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew
context:nil];
}
- (IBAction)change:(id)sender {
// [person willChangeValueForKey:@"name"];
person.name = @"ADA";
// [person didChangeValueForKey:@"name"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"keyPath = %@",keyPath);
NSLog(@"object = %@",object);
NSLog(@"change = %@",change);
}
- (void)dealloc {
[person removeObserver:self forKeyPath:@"name" context:nil];
}
這個(gè)是常見(jiàn)的KVO。
其實(shí)這個(gè)是自動(dòng)實(shí)現(xiàn)的KVO還有手動(dòng)實(shí)現(xiàn)的KVO
將上訴注釋掉的代碼打開(kāi)即可實(shí)現(xiàn)。
KVO的底層是通過(guò)isa-swizzling實(shí)現(xiàn)的。官方文檔中第一段有提到
- Automatic key-value observing is implemented using a technique called isa-swizzling
那么這個(gè)isa-swizzling是什么呢?
大家可能對(duì)Method-Swizzling會(huì)比較熟悉,它的實(shí)現(xiàn)其實(shí)是一個(gè)替換函數(shù)實(shí)現(xiàn)指針的過(guò)程。

具體實(shí)現(xiàn)的代碼:
+ (void)swizzleWithOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector isClassMethod:(BOOL)isClassMethod {
Class class = [self class];
Method originalMethod;
Method swizzledMethod;
if (isClassMethod) {
originalMethod = class_getClassMethod(class, originalSelector);
swizzledMethod = class_getClassMethod(class, swizzledSelector);
}else {
originalMethod = class_getInstanceMethod(class, originalSelector);
swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
}
if (!originalMethod) {
NSLog(@"original is nil (%@)",originalMethod);
}
method_exchangeImplementations(originalMethod, swizzledMethod);
}
那么isa-swizzling顧名思義就是替換isa的過(guò)程。
那isa又是什么呢?
oc是面向?qū)ο蟮恼Z(yǔ)言,每一個(gè)對(duì)象都是一個(gè)類的實(shí)例。
每個(gè)對(duì)象都有一個(gè)名為isa的指針,指向該對(duì)象的類。每個(gè)類中又描述了它的實(shí)例的特點(diǎn),比如成員變量列表,成員函數(shù)列表。每一個(gè)對(duì)象都可以接收消息,而對(duì)象能夠接收的消息列表都保存在它所對(duì)應(yīng)的類中。NSObject就是一個(gè)包含isa指針的結(jié)構(gòu)體

從Class的定義中,我們也可以看出Class也是一個(gè)包含isa指針的結(jié)構(gòu)體。每一個(gè)類實(shí)際上也是一個(gè)對(duì)象,每一個(gè)類也有一個(gè)名為isa的指針。

既然每一個(gè)類也是一個(gè)對(duì)象,那它必然是另一個(gè)類的實(shí)例。這個(gè)類就是元類(meta)。元類也是一個(gè)對(duì)象。元類的isa指針都指向一個(gè)根元類.根元類本身的isa指針指向自己。

這一塊可能有點(diǎn)繞。
個(gè)人理解的isa就是一個(gè)Class 類型的指針. 每個(gè)實(shí)例對(duì)象都有一個(gè)isa的指針,指向該對(duì)象的類,而Class里也有個(gè)isa的指針, 指向meteClass(元類)。同樣的,元類也是類,它也是對(duì)象。元類也有isa指針,最終指向的是一個(gè)根元類(root meteClass).根元類的isa指針指向本身,這樣形成了一個(gè)封閉的內(nèi)循環(huán)。
isa-swizzling就是在運(yùn)行時(shí)動(dòng)態(tài)地修改 isa 指針的值,達(dá)到替換對(duì)象整個(gè)行為的目的。
既然是替換了類,那么在添加了KVO之后這個(gè)類究竟做了什么改變。
我們可以通過(guò)object_getClass()來(lái)打印出isa指針。
NSLog(@"%@",object_getClass(person));
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"%@",object_getClass(person));
運(yùn)行后可以在控制臺(tái)看到:
- 2017-04-19 16:49:55.277 KVODemo[1759:1701820] Person
- 2017-04-19 16:49:55.278 KVODemo[1759:1701820] NSKVONotifying_Person
也就是說(shuō)pesron對(duì)象的isa指針已經(jīng)指向了NSKVONotifying_Person類了。
那這個(gè)NSKVONotifying_Person類究竟是什么呢?
在網(wǎng)上查閱后發(fā)現(xiàn),這個(gè)NSKVONotifying_Person是Person的一個(gè)子類。
我們可以通過(guò)class_getSuperclass來(lái)驗(yàn)證。
@implementation Person
- (void)print{
NSLog(@"isa:%@, supper class:%@", NSStringFromClass(object_getClass(self)), class_getSuperclass(object_getClass(self)));
}
@end
然后再添加KVO之前和之后分別調(diào)用這個(gè)方法,可以在控制臺(tái)看到:
- 2017-04-19 17:43:33.311 KVODemo[1899:1927921] isa:Person, supper class:NSObject
- 2017-04-19 17:43:33.312 KVODemo[1899:1927921] isa:NSKVONotifying_Person, supper class:Person
所以可以知道NSKVONotifying_Person是Person的子類。
然后還有一點(diǎn)就是系統(tǒng)是自動(dòng)實(shí)現(xiàn)監(jiān)聽(tīng)類的屬性,那么set方法就有可能被重寫了,因?yàn)橄C(jī)制是通過(guò)isa查找的,如果子類中沒(méi)有對(duì)應(yīng)的方法,就會(huì)在父類中查找,但是我們?cè)赑erson中并沒(méi)有寫willChangeValueForKey:和didChangeValueForKey:這兩個(gè)方法。所以肯定也是在子類中實(shí)現(xiàn)的。
在print方法里 在加入一條打印
NSLog(@"name setter function pointer:%p", class_getMethodImplementation(object_getClass(self), @selector(setName:)));
運(yùn)行后控制臺(tái)顯示:
- name setter function pointer:0x10c265740
- name setter function pointer:0x10c368c60
證明set方法確實(shí)是被重寫了。
到這里基本可以確定KVO的實(shí)現(xiàn)是:
添加觀察后:
系統(tǒng)實(shí)現(xiàn)了一個(gè)子類,然后將被觀察的類對(duì)象的isa指針指向這個(gè)子類。再重寫了setter方法。并在當(dāng)中添加了willChangeValueForKey:和didChangeValueForKey:。
移除觀察就是將isa指針指向原來(lái)的類對(duì)象中。
那么isa-swizzling做的處理應(yīng)該是這樣的:

大概就這樣子,如果有什么不對(duì)的地方,歡迎大家提出來(lái)。共同進(jìn)步。
覺(jué)得對(duì)你有幫助給個(gè)喜歡。