KVC和KVO的底層原理

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ò)程。

method-swizzling
具體實(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)體

NSObject的定義頭文件

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

Class的定義頭文件

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


class-diagram.jpg

這一塊可能有點(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)該是這樣的:


isa-swizzling.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容