iOS-探討KVO

KVO是現(xiàn)在面試的時(shí)候,算是一個(gè)必問的環(huán)節(jié),筆者也是在之前做面試準(zhǔn)備的時(shí)候,查看了一些資料,然后最近又回顧了一下,在這里算是做一個(gè)總結(jié)。筆者還是按照老樣子,按照資料里面的環(huán)節(jié),從面試題入手來梳理KVO的知識(shí)點(diǎn)。如下三個(gè)問題:

問題一:iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO?(KVO的本質(zhì)是什么)
問題二:如何手動(dòng)觸發(fā)KVO?
問題三:直接修改成員變量會(huì)觸發(fā)KVO嗎?

在來看第一個(gè)問題之前,筆者要告訴大家,KVO的本質(zhì)涉及到的知識(shí)點(diǎn)有OC對(duì)象的本質(zhì)的知識(shí)點(diǎn),比如對(duì)象中的isa、superClass,OC的類信息等等。如果大家想要了解這些知識(shí)點(diǎn),可以看看筆者的iOS-Objective-C的本質(zhì)這篇文章,回顧下知識(shí)點(diǎn)。

現(xiàn)在,我們先來復(fù)習(xí)下KVO的運(yùn)用,代碼如下:

@interface MJPerson : NSObject
@property (assign, nonatomic) int age;
@property (assign, nonatomic) int height;
@end

@implementation MJPerson
@end

@interface ViewController ()
@property (strong, nonatomic) MJPerson *person1;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person1 = [[MJPerson alloc] init];
    self.person1.age = 1;
    self.person1.height = 11;
    
    // 給person1對(duì)象添加KVO監(jiān)聽
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
    [self.person1 addObserver:self forKeyPath:@"height" options:options context:@"456"];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.person1.age = 20;
    self.person2.age = 20;
}

- (void)dealloc {
    [self.person1 removeObserver:self forKeyPath:@"age"];
    [self.person1 removeObserver:self forKeyPath:@"height"];
}

// 當(dāng)監(jiān)聽對(duì)象的屬性值發(fā)生改變時(shí),就會(huì)調(diào)用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"監(jiān)聽到%@的%@屬性值改變了 - %@ - %@", object, keyPath, change, context);
}

@end

通過以上代碼,在點(diǎn)擊屏幕時(shí),觸發(fā)了KVO事件,然后打印出了結(jié)果如下:


image.png

這個(gè)結(jié)果也是我們之前經(jīng)常使用KVO的結(jié)果顯示

現(xiàn)在在監(jiān)聽KVO之前和之后兩處打斷點(diǎn),如圖:


image.png

然后依次打印出person所對(duì)應(yīng)的isa指針,得到如下的圖:


image.png

在這里大家都會(huì)發(fā)現(xiàn),其實(shí)在用KVO過程中,其實(shí)isa指針指向的對(duì)象不是同一個(gè),那為什么會(huì)有這種現(xiàn)象呢?其實(shí)大家仔細(xì)想想也是可以想到的,因?yàn)閷?duì)象的設(shè)置,無非就是setvalue方法,但是大家都知道一個(gè)instance對(duì)象的方法是放在對(duì)象的class對(duì)象中的,所以對(duì)于同一個(gè)類,setvalue方法肯定是一樣的 就像上述代碼中的
self.person1.age = 20;

其實(shí)在對(duì)象的class對(duì)象中,它的方法就是:

- (void)setAge:(int)age {
    _age = age;
}

所以,如果在不改動(dòng)代碼的情況下,如何能監(jiān)聽對(duì)象的變化呢?所以系統(tǒng)在使用KVO的時(shí)候,會(huì)將instance對(duì)象isa指向自己的一個(gè)NSKVONotifying_Class對(duì)象,而在NSKVONotifying_Class對(duì)象中,它會(huì)重寫setValue這個(gè)方法;此外,NSKVONotifying_Class這個(gè)對(duì)象的superClass就是我們?cè)鹊腃lass類,所以NSKVONotifying_Class就是原類的子類,它也不會(huì)重寫get方法。
既然,NSKVONotifying_Class重寫了set方法,那set方法是如何監(jiān)聽的呢?其實(shí)在重寫的set方法中調(diào)用了NSSetIntValueAndNotify的C函數(shù),當(dāng)然這個(gè)方法不是固定的,因?yàn)閍ge是int類型,所以調(diào)用NSSetIntValueAndNotify,如果是double類型,他就是調(diào)用NSSetDoubleValueAndNotify。而在NSSetIntValueAndNotify中,其實(shí)它調(diào)用了willChangeValueForKey:、setValue:(原set方法)didChangeValueForKey:這三個(gè)方法:代碼如下:

_ NSSetIntValueAndNotify() {
willChangeValueForKey:
setValue:(原set方法)
didChangeValueForKey:
}

當(dāng)然,這寫的偽代碼,只是展示了重寫的setValue方法中的過程。
這樣,基本上監(jiān)聽實(shí)現(xiàn)的過程就很清楚了:

1 給對(duì)象的的屬性或者成員變量添加KVO
2 系統(tǒng)根據(jù)原類Class,動(dòng)態(tài)創(chuàng)建一個(gè)NSKVONotifying_Class對(duì)象
3 在NSKVONotifying_Class對(duì)象中,重寫setValue方法
4 在重寫的setValue方法中,實(shí)現(xiàn)監(jiān)聽

到此,問題一的答案已經(jīng)知曉

現(xiàn)在我們來看看問題二:如何手動(dòng)觸發(fā)KVO?

其實(shí)這個(gè)問題,筆者在敘述KVO的實(shí)現(xiàn)中已經(jīng)有展示了,剛才說

_ NSSetIntValueAndNotify() {
willChangeValueForKey:
setValue:(原set方法)
didChangeValueForKey:
}

這個(gè)過程就是監(jiān)聽的實(shí)現(xiàn)過程,所以,只要手動(dòng)調(diào)用willChangeValueForKey:和didChangeValueForKey:這兩個(gè)方法,不管監(jiān)聽的對(duì)象是否有變化,它都會(huì)走監(jiān)聽過程,大家不妨可以試試,代碼如下:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.person1 willChangeValueForKey:@"age"];
    [self.person1 didChangeValueForKey:@"age"];
}

// 當(dāng)監(jiān)聽對(duì)象的屬性值發(fā)生改變時(shí),就會(huì)調(diào)用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"監(jiān)聽到%@的%@屬性值改變了 - %@ - %@", object, keyPath, change, context);
}

控制臺(tái)打印:


image.png

所以,第二個(gè)問題的答案就是:

只要手動(dòng)調(diào)用willChangeValueForKey:和didChangeValueForKey:這兩個(gè)方法,不管監(jiān)聽的對(duì)象是否有變化,它就會(huì)調(diào)用KVO

對(duì)于問題三:直接修改成員變量會(huì)觸發(fā)KVO嗎?

其實(shí)是不會(huì)的,因?yàn)橹苯有薷某蓡T變量,相當(dāng)于直接賦值,如_age=10,因?yàn)檫@個(gè)時(shí)候不會(huì)走setValue方法,既然不會(huì)走setValue方法,那么也不會(huì)走NSKVONotifying_Class這個(gè)對(duì)象的setValue方法,所以不會(huì)觸發(fā)KVO
最后編輯于
?著作權(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)容