iOS KVO 底層原理

什么是KVO擂达?

KVO是一種機(jī)制,他是建立在KVC的基礎(chǔ)上的胶滋,他可以將其他對(duì)象屬性值的變化通知給對(duì)象板鬓。

1.1、注冊(cè)KVO

您必須執(zhí)行以下步驟究恤,才能使對(duì)象能夠接收KVO兼容屬性的鍵值觀察通知:

  • 使用方法addObserver:forKeyPath:options:context:將觀察者注冊(cè)到觀察對(duì)象俭令。
  • observeValueForKeyPath:ofObject:change:context:在觀察者內(nèi)部實(shí)現(xiàn)這個(gè)方法以接收更改通知消息。
  • removeObserver:forKeyPath:當(dāng)觀察者不再需要接收消息時(shí)部宿,使用該方法注銷觀察者抄腔。最晚在從內(nèi)存釋放觀察者之前調(diào)用此方法。
  • removeObserver:forKeyPath:context:當(dāng)我們?cè)谧?cè)觀察者的時(shí)候理张,如果context參數(shù)不為NULL時(shí)赫蛇,應(yīng)該使用這個(gè)方法來(lái)移除,這樣更安全雾叭。

1.2悟耘、context參數(shù)解釋

addObserver:forKeyPath:options:context:方法中的context參數(shù)將在相應(yīng)的observeValueForKeyPath:ofObject:change:context:中回傳給觀察者。你可以將這個(gè)參數(shù)指定為NULL织狐,通過(guò)依賴keyPath來(lái)確定觀察屬性的來(lái)源暂幼,但是當(dāng)有多個(gè)對(duì)象具有相同的屬性被觀察時(shí)掘殴,根據(jù)keyPath來(lái)判斷就顯得不那么方便了。

一種更安全粟誓,更具擴(kuò)展性的方法是使用context來(lái)進(jìn)行區(qū)分奏寨。

context指針的創(chuàng)建。

static void * PersonAccountBalanceContext =&PersonAccountBalanceContext;
static void * PersonAccountInterestRateContext =&PersonAccountInterestRateContext;

2.1鹰服、接收KVO的通知

當(dāng)觀察到的對(duì)象屬性值改變時(shí)病瞳,觀察者會(huì)收到一條observeValueForKeyPath:ofObject:change:context: 消息。所有觀察者都必須實(shí)現(xiàn)此方法悲酷。

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
 
    if (context == PersonAccountBalanceContext) {
        // Do something
 
    } else if (context == PersonAccountInterestRateContext) {
        // Do something
 
    } else {
        // Any unrecognized context must belong to super
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                               context:context];
    }
}

當(dāng)我們?cè)谧?cè)觀察者的時(shí)候使用context參數(shù)時(shí)套菜,那么在接收通知的地方就可以使用context來(lái)區(qū)分是哪個(gè)對(duì)象的屬性觸發(fā)了通知回調(diào)。

如果在注冊(cè)觀察者時(shí)用NULL傳遞個(gè)context设易,那么將使用keyPath來(lái)進(jìn)行比較逗柴,已確定是哪個(gè)對(duì)象的屬性進(jìn)行了更改。

無(wú)論如何顿肺,觀察者應(yīng)始終observeValueForKeyPath:ofObject:change:context:在其無(wú)法識(shí)別context(或在簡(jiǎn)單情況下戏溺,是任意的keyPath)時(shí)調(diào)用父類的實(shí)現(xiàn),因?yàn)檫@意味著父類也已注冊(cè)了通知屠尊。

如果通知傳遞到類層次結(jié)構(gòu)的頂部旷祸,則NSObject拋出,NSInternalInconsistencyException因?yàn)檫@是編程錯(cuò)誤:子類無(wú)法使用為其注冊(cè)的通知讼昆。

3托享、移除KVO

通過(guò)向被觀察對(duì)象發(fā)送一條removeObserver:forKeyPath:context:消息,指定observer浸赫,keyPathcontext闰围,可以刪除鍵值觀察者。

移除觀察者時(shí)既峡,請(qǐng)謹(jǐn)記以下幾點(diǎn)羡榴。

  • 如果移除了一個(gè)沒(méi)有注冊(cè)的觀察者,則將會(huì)引發(fā)一個(gè)NSRangeException異常涧狮,你可以將removeObserver:forKeyPath:context:調(diào)用放在try / catch塊中以處理潛在的異常炕矮。
  • 當(dāng)對(duì)象釋放后么夫,觀察者不會(huì)自動(dòng)被移除者冤,如果被觀察對(duì)象也沒(méi)有被釋放,那么被觀察對(duì)象會(huì)繼續(xù)發(fā)送通知档痪,和其他的對(duì)象一樣涉枫,向已釋放的對(duì)象發(fā)送消息,會(huì)觸發(fā)內(nèi)存異常腐螟。為此愿汰,要確保觀察者在對(duì)象釋放之前困后,刪除自己。
  • 該協(xié)議無(wú)法詢問(wèn)對(duì)象是觀察者還是被觀察者衬廷。為了代碼不出現(xiàn)相關(guān)的錯(cuò)誤摇予。一種典型的做法是在觀察者初始化期間(例如在中init或中viewDidLoad)注冊(cè)為觀察者,在釋放過(guò)程中(通常在中dealloc)注銷(確保正確配對(duì)和排序的添加和刪除消息)吗跋,并且在對(duì)象從內(nèi)存中釋放之前將其注銷侧戴。 。

4跌宛、自動(dòng)通知與手動(dòng)通知

KVO默認(rèn)的是自動(dòng)通知酗宋,也就是當(dāng)我們屬性的值變化的時(shí)候,就會(huì)自動(dòng)發(fā)送通知疆拘,我們可以在改類中重寫automaticallyNotifiesObserversForKey:方法來(lái)控制是否啟用自動(dòng)通知蜕猫。

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    return YES;
}
  • 返回為YES時(shí),是為該對(duì)象的所有屬性啟用自動(dòng)通知哎迄。
  • 返回為NO時(shí)回右,是為該對(duì)象的所有對(duì)象禁用自動(dòng)通知。

我們可以根據(jù)Key來(lái)判斷漱挚,為某一個(gè)屬性啟用或者禁用自動(dòng)通知楣黍。

另外針對(duì)特定的屬性啟用和禁用自動(dòng)通知,系統(tǒng)還給我們生成了唯一的方法棱烂。

@interface Account : NSObject
@property (nonatomic, assign) double balance;
@property (nonatomic, assign) double interesRate;
@end

Account中的屬性為例租漂,編譯器為我們自動(dòng)生成了兩個(gè)方法,分別來(lái)控制該屬性是否啟用自動(dòng)通知颊糜。

+ (BOOL)automaticallyNotifiesObserversOfBalance {
    return NO;
}

+ (BOOL)automaticallyNotifiesObserversOfInteresRate {
    return NO;
}

automaticallyNotifiesObserversForKey:方法的優(yōu)先級(jí)大于特定屬性生成的方法哩治,如果實(shí)現(xiàn)了automaticallyNotifiesObserversForKey:方法,那么特定屬性的方法將不會(huì)被調(diào)用衬鱼。

要實(shí)現(xiàn)手動(dòng)觀察者通知业筏,請(qǐng)手動(dòng)調(diào)用willChangeValueForKey:在更改值之前和didChangeValueForKey:更改值之后。以balance屬性實(shí)現(xiàn)了手動(dòng)通知鸟赫。

- (void)setBalance:(double)theBalance {
    [self willChangeValueForKey:@"balance"];
    _balance = theBalance;
    [self didChangeValueForKey:@"balance"];
}

5蒜胖、可變集合的KVO

當(dāng)我們監(jiān)聽的對(duì)象的屬性是可變集合或者是可變數(shù)組時(shí),如果我們想要得到數(shù)組或者集合內(nèi)容變化時(shí)的通知抛蚤,我們需要做一些特殊的處理台谢。

  • 使用mutableArrayValueForKey:方法取出對(duì)象中的數(shù)組,然后在對(duì)可變數(shù)組進(jìn)行操作岁经,此時(shí)我們就可以得到數(shù)組內(nèi)容變化的通知了朋沮。
NSMutableArray *mArray = [self.account mutableArrayValueForKey:@"transactions"];
[mArray addObject:@"4"];
  • 可變集合的操作和這個(gè)類似,使用mutableSetValueForKey:缀壤。

6樊拓、屬性依賴

當(dāng)一個(gè)屬性的值是依賴于其他幾個(gè)屬性來(lái)決定的時(shí)候纠亚,我們可以使用keyPathsForValuesAffectingValueForKey:方法或者使用遵循命名方式的keyPathsForValuesAffectingValueFor<Key>來(lái)建立以來(lái)關(guān)系。

例如筋夏,一個(gè)人的全名取決于名字和姓氏蒂胞。返回全名的方法可以編寫如下:

- (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
}

我們?cè)谕獠勘O(jiān)聽fullName,當(dāng)firstName或者lastName的值發(fā)生改變時(shí)条篷,則應(yīng)該觸發(fā)回調(diào)啤誊。
下面介紹兩種建立依賴的方法。

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
 
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
 
    if ([key isEqualToString:@"fullName"]) {
        NSArray *affectingKeys = @[@"lastName", @"firstName"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}
+ (NSSet *)keyPathsForValuesAffectingFullName {
    return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}

KVO實(shí)現(xiàn)原理

KVO是使用isa-swizzling技術(shù)實(shí)現(xiàn)的拥娄,簡(jiǎn)單來(lái)說(shuō)就是修改了對(duì)象的isa指針蚊锹,使其指向中間類而不是真正的類,所以isa指針的值并不能反映實(shí)例的實(shí)際類稚瘾,所以應(yīng)該使用class方法來(lái)確定對(duì)象的實(shí)際類牡昆。

1.1、KVO驗(yàn)證

接下來(lái)我們就做一個(gè)簡(jiǎn)單的驗(yàn)證摊欠。
現(xiàn)在我們有一個(gè)Person

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end

我們分別在添加KVO之前和添加KVO只有來(lái)輸出對(duì)象的isa指針看看丢烘。

self.person = [Person new];
{
    Class cls = object_getClass(self.person);
    NSLog(@"%@", NSStringFromClass(cls));
}

[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
{
    Class cls = object_getClass(self.person);
    NSLog(@"%@", NSStringFromClass(cls));
}

輸出結(jié)果如下

2020-02-14 10:24:32.252254+0800 KVO原理探索[23368:1376988] Person
2020-02-14 10:24:32.252750+0800 KVO原理探索[23368:1376988] NSKVONotifying_Person

我們發(fā)現(xiàn)兩次輸出的結(jié)果不一樣,對(duì)象沒(méi)有添加KVO之前些椒,isa指針指向的是Person類播瞳,添加了KVO之后對(duì)象的isa指針,指向的是NSKVONotifying_Person免糕。至此我們可以得出對(duì)象在添加KVO之后赢乓,在運(yùn)行時(shí)為我們動(dòng)態(tài)的生成了一個(gè)NSKVONotifying_Person的類,并且將這個(gè)對(duì)象的isa指針指向了這個(gè)新的類石窑。

1.2牌芋、動(dòng)態(tài)類的繼承關(guān)系

我們都知道,在OC中松逊,所有的類躺屁,都有一個(gè)父類,我們來(lái)看看NSKVONotifying_Person的繼承關(guān)系经宏。

Class cls = object_getClass(self.person);
NSLog(@"%@", NSStringFromClass(cls));

Class supCls = cls;
do {
    supCls = [supCls superclass];
    NSLog(@"%@", NSStringFromClass(supCls));
} while (supCls);

這段代碼將會(huì)輸出類的所有父類犀暑。

2020-02-14 10:37:24.826558+0800 KVO原理探索[23558:1388700] NSKVONotifying_Person
2020-02-14 10:37:24.826718+0800 KVO原理探索[23558:1388700] Person
2020-02-14 10:37:24.826840+0800 KVO原理探索[23558:1388700] NSObject
2020-02-14 10:37:24.826945+0800 KVO原理探索[23558:1388700] (null)

通過(guò)驗(yàn)證,我們發(fā)現(xiàn)NSKVONotifying_Person是直接繼承與Person的烁兰。

1.3耐亏、動(dòng)態(tài)類方法探究

接下來(lái)我們看看這個(gè)動(dòng)態(tài)生成的類中都有那寫方法,我們使用Runtime的API來(lái)輸出這個(gè)類中的所有方法以及他們的實(shí)現(xiàn)缚柏。

- (void)printClassAllMethod:(Class)cls {
    unsigned int count = 0;
    Method *methods = class_copyMethodList(cls, &count);
    for (int i = 0; i < count; i++) {
        Method method = methods[i];
        SEL methodSel = method_getName(method);
        IMP methodImp = method_getImplementation(method);
        NSLog(@"%@-%p", NSStringFromSelector(methodSel), methodImp);
    }
    free(methods);
}

調(diào)用上面的這段代碼就可以輸出這個(gè)類中都定義了哪些方法苹熏,我們來(lái)看看NSKVONotifying_Person中都有哪些碟贾。

2020-02-14 11:43:09.706699+0800 KVO原理探索[24582:1444989] setName:-0x7fff25721c7a
2020-02-14 11:43:09.706794+0800 KVO原理探索[24582:1444989] class-0x7fff2572073d
2020-02-14 11:43:09.706871+0800 KVO原理探索[24582:1444989] dealloc-0x7fff257204a2
2020-02-14 11:43:09.706973+0800 KVO原理探索[24582:1444989] _isKVOA-0x7fff2572049a

我們發(fā)現(xiàn)它重寫了三個(gè)方法并且自定義了一個(gè)方法币喧,最主要的是它重寫了屬性的setter方法轨域。

這里我們也輸出一下Person類中的所有方法。

2020-02-14 11:41:12.101584+0800 KVO原理探索[24582:1444989] .cxx_destruct-0x108053ee0
2020-02-14 11:41:12.101732+0800 KVO原理探索[24582:1444989] name-0x108053e70
2020-02-14 11:41:12.101854+0800 KVO原理探索[24582:1444989] setName:-0x108053ea0

接下來(lái)我們來(lái)看看杀餐,添加KVO之后設(shè)置屬性時(shí)干发,有哪些變化。


沒(méi)有添加KVO

我們可以看到對(duì)象在沒(méi)有添加KVO時(shí)史翘,直接調(diào)用了屬性的setter方法對(duì)屬性進(jìn)行賦值枉长。通過(guò)方法的地址可以驗(yàn)證。

2020-02-14 11:41:12.101854+0800 KVO原理探索[24582:1444989] setName:-0x108053ea0

上面setter方法的地址和調(diào)用地址是一樣的琼讽,由此可以得出是直接調(diào)用了setter方法必峰。


添加了KVO

當(dāng)對(duì)象在添加了KVO之后,我們?cè)賹?duì)屬性進(jìn)行賦值的時(shí)候調(diào)用的不一樣了钻蹬。我們發(fā)現(xiàn)這里調(diào)用的方法的地址就是我們動(dòng)態(tài)類中setter方法的地址吼蚁。

2020-02-14 11:43:09.706699+0800 KVO原理探索[24582:1444989] setName:-0x7fff25721c7a

所以當(dāng)對(duì)象添加了KVO之后,再對(duì)屬性進(jìn)行賦值時(shí)調(diào)用的是動(dòng)態(tài)類中重寫的方法问欠。在這個(gè)方法中我們發(fā)現(xiàn)它調(diào)用了willChangeValueForKey:didChangeValueForKey:肝匆,根據(jù)官網(wǎng)的介紹可知,這兩個(gè)方法是用來(lái)發(fā)送通知的顺献。

調(diào)用堆棧

最后調(diào)用父類的setter方法來(lái)賦值旗国。
調(diào)用堆棧

2、原理總結(jié)

  • 監(jiān)聽者監(jiān)聽Person對(duì)象的某一個(gè)屬性的變化注整,系統(tǒng)會(huì)動(dòng)態(tài)為類Person創(chuàng)建一個(gè)子類NSKVONotifying_Person能曾,并將Person對(duì)象的isa指針重新指向該子類
  • 系統(tǒng)會(huì)重寫Person對(duì)象的setter方法。( 賦值前后分別調(diào)用willChangeValueForKeydidChangeValueForKey跟蹤新舊值 )肿轨。在對(duì)象賦值時(shí)是調(diào)用父類的setter方法來(lái)處理的借浊。
  • 當(dāng)Person對(duì)象的屬性發(fā)生改變時(shí),系統(tǒng)通知監(jiān)聽者萝招,調(diào)用observeValueForKey:ofObject:change:context方法即可蚂斤。

問(wèn)題。
當(dāng)我們的對(duì)象添加了KVO之后槐沼,為什么通過(guò)class方法獲取到的類是Person呢曙蒸?
因?yàn)?code>NSKVONotifying_Person重寫了class方法,在這個(gè)方法中返回為Person岗钩。但是object_getClass獲取到的是isa指針纽窟,所以調(diào)用object_getClass返回的是NSKVONotifying_Person

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末兼吓,一起剝皮案震驚了整個(gè)濱河市臂港,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖审孽,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件县袱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡佑力,警方通過(guò)查閱死者的電腦和手機(jī)式散,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)打颤,“玉大人暴拄,你說(shuō)我怎么就攤上這事”嘟龋” “怎么了乖篷?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)透且。 經(jīng)常有香客問(wèn)我那伐,道長(zhǎng),這世上最難降的妖魔是什么石蔗? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任罕邀,我火速辦了婚禮,結(jié)果婚禮上养距,老公的妹妹穿的比我還像新娘诉探。我一直安慰自己,他們只是感情好棍厌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布肾胯。 她就那樣靜靜地躺著,像睡著了一般耘纱。 火紅的嫁衣襯著肌膚如雪敬肚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天束析,我揣著相機(jī)與錄音艳馒,去河邊找鬼。 笑死员寇,一個(gè)胖子當(dāng)著我的面吹牛弄慰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蝶锋,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼陆爽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了扳缕?” 一聲冷哼從身側(cè)響起慌闭,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤别威,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后驴剔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體省古,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年仔拟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了衫樊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片飒赃。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡利花,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出载佳,到底是詐尸還是另有隱情炒事,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布蔫慧,位于F島的核電站挠乳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏姑躲。R本人自食惡果不足惜睡扬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望黍析。 院中可真熱鬧卖怜,春花似錦、人聲如沸阐枣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蔼两。三九已至甩鳄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間额划,已是汗流浹背妙啃。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓荣堰,卻偏偏與公主長(zhǎng)得像酱塔,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子罗侯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 問(wèn)題 iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO?(KVO的本質(zhì)是什么?) 如何手動(dòng)觸發(fā)KVO 突倍? 首先需要了解KVO...
    hjltony閱讀 579評(píng)論 0 2
  • 前言 KVO作為iOS一個(gè)設(shè)計(jì)模式腔稀,監(jiān)聽對(duì)象屬性變化。通過(guò)屬性變化來(lái)做出一些處理羽历。那么KVO底層原理是什么焊虏?相信大...
    楓葉無(wú)處漂泊閱讀 804評(píng)論 0 2
  • KVO的全稱是Key-Value Observing,俗稱鍵值監(jiān)聽秕磷,可以用于監(jiān)聽某個(gè)對(duì)象屬性值的改變诵闭。下面我們來(lái)了...
    Goose的小黃花閱讀 467評(píng)論 0 2
  • iOS底層原理總結(jié) - 探尋KVO本質(zhì) 對(duì)小碼哥底層班視頻學(xué)習(xí)的總結(jié)與記錄。 面試題:iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)...
    愛(ài)吃兔兔的胡蘿卜吖閱讀 292評(píng)論 0 1
  • 一澎嚣、概述 KVO疏尿,即:Key-Value Observing,它提供一種機(jī)制易桃,當(dāng)指定的對(duì)象的屬性被修改后褥琐,則其觀察...
    DeerRun閱讀 10,061評(píng)論 11 33