iOS KVO原理分析

前言

上一篇文章學(xué)習(xí)了KVC的原理(鍵值編碼),KVC是由NSKeyValueCoding非正式協(xié)議啟用的一種機(jī)制,對(duì)象采用該機(jī)制來(lái)提供對(duì)其屬性的間接訪問(wèn)。而KVO的實(shí)現(xiàn)是基于KVC鍵值編碼,以下我們進(jìn)行探討。

準(zhǔn)備工作

鍵值觀察編程指南

KVO協(xié)議定義

KVO全稱是Key-value Observing,翻譯過(guò)來(lái)就是:鍵值觀察。提供了一種當(dāng)其它對(duì)象屬性被修改的時(shí)候能通知當(dāng)前對(duì)象的機(jī)制。

  • KVO的定義
    類似于KVCKVO的定義都是對(duì)NSObject擴(kuò)展來(lái)實(shí)現(xiàn)的,KVO的定義在Foundation里面,而Foundation框架是不開源的,只能在蘋果官方文檔查找。見下圖:
    KVO分類定義

    KVO鍵值觀察)是一種機(jī)制,它允許對(duì)象在其他對(duì)象的指定屬性發(fā)生更改時(shí)收到通知。它對(duì)于應(yīng)用程序中模型層和控制器層之間的通信特別有用。

注意:要使用KVO,首先必須確保被觀察對(duì)象符合KVO。一般情況下,如果您的對(duì)象繼承自NSObject并且您以通常的方式創(chuàng)建屬性,則您的對(duì)象及其屬性將自動(dòng)符合KVO。

  • KVO提供的API
  • 監(jiān)聽注冊(cè)
    使用方法addObserver:forKeyPath:options:context:向被觀察對(duì)象注冊(cè)觀察者。必須執(zhí)行以下步驟才能使對(duì)象能夠接收KVO兼容屬性的鍵值觀察通知,觀察者指定一個(gè)選項(xiàng)參數(shù)options和一個(gè)上下文指針context來(lái)管理通知的各個(gè)方面。
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
  • 接收通知
    在觀察者內(nèi)部實(shí)現(xiàn)observeValueForKeyPath:ofObject:change:context:以接受更改通知消息。
 - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
  • 移除監(jiān)聽
    使用方法- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;移除觀察者
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

KVO的使用

監(jiān)聽選項(xiàng)option

監(jiān)聽選項(xiàng)是由枚舉NSKeyValueObservingOptions定義的:

    typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
            NSKeyValueObservingOptionNew = 0x01,
            NSKeyValueObservingOptionOld = 0x02,
            NSKeyValueObservingOptionInitial API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x04,
            NSKeyValueObservingOptionPrior API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x08
    }

注意:option會(huì)影響通知中,提供的更改字典的內(nèi)容以及生成通知的方式

  • NSKeyValueObservingOptionNew
    監(jiān)聽獲取屬性的新值,見下圖:
    監(jiān)聽獲取屬性新值
  • NSKeyValueObservingOptionOld
    監(jiān)聽屬性獲取舊值,見下圖:
    監(jiān)聽屬性獲取舊值
  • NSKeyValueObservingOptionInitial
    添加觀察者的時(shí)候立即發(fā)送一個(gè)通知給觀察者,見下圖:
    發(fā)送通知給觀察者

    在每次修改屬性時(shí),會(huì)在修改通知被發(fā)送之前預(yù)先發(fā)送一條通知給觀察者,這與-willChangeValueForKey:被觸發(fā)的時(shí)間是相對(duì)應(yīng)的。這樣,在每次修改屬性時(shí),實(shí)際上是會(huì)發(fā)送兩條通知,見下圖:
    修改屬性發(fā)送兩條通知

上下文指針Context

addObserver:forKeyPath:options:context:消息中的上下文指針包含任意數(shù)據(jù),這些數(shù)據(jù)將在相應(yīng)的更改通知中傳遞回觀察者。您可以指定NULL并完全依賴鍵路徑字符串來(lái)確定更改通知的來(lái)源,但這種方法可能會(huì)導(dǎo)致超類由于不同原因也在觀察相同鍵路徑的對(duì)象出現(xiàn)問(wèn)題。

一下實(shí)現(xiàn)一個(gè)案例,LGStudent繼承自LGPerson,同時(shí)對(duì)兩個(gè)對(duì)象的name屬行進(jìn)行設(shè)置,通過(guò)添加上下文指針context,可以在接收通知的地方進(jìn)行過(guò)濾。見下圖:

案例分析

KVO使用技巧

  • 同一個(gè)對(duì)象重復(fù)注冊(cè)為同一屬性的觀察者
    可以多次調(diào)用addObserver:forKeyPath:options:context:這個(gè)方法,將同一個(gè)對(duì)象注冊(cè)為同一屬性的觀察者(所有參數(shù)可以完全相同)。這時(shí),即便在所有參數(shù)一致的情況下,新注冊(cè)的觀察者并不會(huì)替換原來(lái)觀察者,而是會(huì)并存。這樣,當(dāng)屬性被修改時(shí),兩次監(jiān)聽都會(huì)響應(yīng)。見下面的案例分析:

    案例分析

    通過(guò)以上的案例,KVO注冊(cè)多少次就會(huì)有多少次的回調(diào),不會(huì)覆蓋相同的觀察者。

  • 移除觀察者
    在觀察者不再需要監(jiān)聽屬性變化時(shí),必須調(diào)用removeObserver:forKeyPath:removeObserver:forKeyPath:context:方法來(lái)移除觀察者,這兩個(gè)方法的聲明如下:

- (void)removeObserver:(NSObject *)anObserver
            forKeyPath:(NSString *)keyPath

- (void)removeObserver:(NSObject *)observer
            forKeyPath:(NSString *)keyPath
               context:(void *)context

這兩個(gè)方法會(huì)根據(jù)傳入的參數(shù)(主要是keyPathcontext)來(lái)移除觀察者。移除觀察者可以避免監(jiān)聽回調(diào)的混亂,保持良好的代碼質(zhì)量

注意:如果observer沒有監(jiān)聽keyPath屬性,依然調(diào)用上面兩個(gè)方法會(huì)拋出異常,見下圖:

拋出異常

由以上案例可知,觀察者的移除是必須確認(rèn)觀察者已經(jīng)被注冊(cè)了,這樣子才能調(diào)用移除觀察者的方法,如果我們沒有移除觀察者也會(huì)出現(xiàn)崩潰的情況,請(qǐng)往下看。
添加觀察時(shí),兩個(gè)對(duì)象(即觀察者對(duì)象及屬性所屬的對(duì)象)都不會(huì)被retain,然而在這些對(duì)象被釋放后,相關(guān)的監(jiān)聽信息卻還存在,KVO做的處理是直接讓程序崩潰。其實(shí)蘋果官網(wǎng)也給出了相關(guān)說(shuō)明,見下圖:
官方說(shuō)明

    • 如果尚未注冊(cè)為觀察者,則要求將其移除為觀察者會(huì)導(dǎo)致NSRangeException。
    • 解除分配時(shí),觀察者不會(huì)自動(dòng)刪除自己。被觀察的對(duì)象繼續(xù)發(fā)送通知,而忽略了觀察者的狀態(tài)。但是,發(fā)送到已釋放對(duì)象的更改通知與任何其他消息一樣,會(huì)觸發(fā)內(nèi)存訪問(wèn)異常。因此,您要確保觀察者在從記憶中消失之前將自己移除。
    • 該協(xié)議沒有提供詢問(wèn)對(duì)象是觀察者還是被觀察者的方法。所以在構(gòu)建代碼時(shí),避免與發(fā)布相關(guān)的錯(cuò)誤。
  • 自動(dòng)監(jiān)聽和手動(dòng)監(jiān)聽

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key

默認(rèn)情況下,該方法返回YES,即表示默認(rèn)可以對(duì)任何類中的所有屬性進(jìn)行監(jiān)聽,可以理解為自動(dòng)監(jiān)聽。在這種模式下,當(dāng)我們修改屬性的值時(shí),KVO會(huì)自動(dòng)調(diào)用以下兩個(gè)方法:

- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key

開發(fā)過(guò)程中,可能不需要對(duì)所有屬性進(jìn)行監(jiān)聽,只要求選擇性的觀察部分屬性。此時(shí)+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key方法返回NO,那么就需要對(duì)屬性進(jìn)行手動(dòng)監(jiān)聽。見下面代碼:

// 自動(dòng)監(jiān)聽開關(guān)-關(guān)閉
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
    return NO;
}

- (void)setName:(NSString *)name{
    [self willChangeValueForKey:@"name"];
    _name = name;
    [self didChangeValueForKey:@"name"];
}

此時(shí)自動(dòng)監(jiān)聽開關(guān)已經(jīng)關(guān)閉,如果需要監(jiān)聽person對(duì)象的name屬性的變化,就需要在setter方法中添加willChangeValueForKeydidChangeValueForKey方法,兩個(gè)方法必須成對(duì)出現(xiàn),否則無(wú)效

如果我們?cè)陂_發(fā)過(guò)程中只是針對(duì)某幾個(gè)屬性進(jìn)行手動(dòng)接收通知,其他的不需要手動(dòng)接收通知,那么我們可以精確的做到這個(gè)動(dòng)作,通過(guò)+automaticallyNotifiesObserversForKey:方法可以設(shè)置對(duì)象中哪些屬性需要手動(dòng)處理,那么可以自動(dòng)處理。見下圖:

案例

  • 確保屬性發(fā)生變化發(fā)送通知
    如果希望只有當(dāng)屬性值實(shí)際被修改時(shí)發(fā)送通知,以盡量減少不必要的通知,則可以如下實(shí)現(xiàn):(做屬性的攔截判斷)
    - (void)setNick:(NSString *)nick{
        if (nick != _nick){
            [self willChangeValueForKey:@"nick"];
            _nick = nick;
            [self didChangeValueForKey:@"nick"];
        }
    }

補(bǔ)充:如果我們?cè)?code>setter方法之外改變了實(shí)例變量(如_nick),且希望這種修改被觀察者監(jiān)聽到,則需要像在setter方法里面做一樣的處理。這也涉及到我們通常會(huì)遇到的一個(gè)問(wèn)題,在類的內(nèi)部,對(duì)于一個(gè)屬性值,何時(shí)用屬性(self.nick)訪問(wèn),而何時(shí)用實(shí)例變量(_nick)訪問(wèn)。一般的建議是,在獲取屬性值時(shí),可以用實(shí)例變量;在設(shè)置屬性值時(shí),盡量用setter方法,以保證屬性的KVO特性。當(dāng)然,性能也是一個(gè)考量,在設(shè)置值時(shí),使用實(shí)例變量比使用屬性設(shè)置值的性能高不少

  • 多屬性依賴
    我們監(jiān)聽的某個(gè)屬性可能會(huì)依賴于其它多個(gè)屬性的變化,不管所依賴的哪個(gè)屬性發(fā)生了變化,都會(huì)導(dǎo)致計(jì)算屬性的變化。對(duì)于這種一對(duì)一(To-one)的關(guān)系,我們需要做兩步操作,首先是確定計(jì)算屬性與所依賴屬性的關(guān)系。如我們?cè)?code>Person類中定義一個(gè)fullName屬性,其getter方法定義如下:
    - (NSString *)fullName {
        return [[NSString alloc] initWithFormat:@"he's full name is :%@ , %@", self.name, self.nick];
    }

定義了這種依賴關(guān)系后,需要以某種方式告訴KVO,當(dāng)我們的被依賴屬性修改時(shí),會(huì)發(fā)送fullName屬性被修改的通知。此時(shí),我們需要重寫NSKeyValueObserving協(xié)議的keyPathsForValuesAffectingValueForKey:方法,這個(gè)方法返回的是一個(gè)集合對(duì)象,包含了哪些影響key指定的屬性依賴的屬性所對(duì)應(yīng)的字符串。所以對(duì)于fullName屬性,該方法的實(shí)現(xiàn)如下:

    + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
        NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
        if ([key isEqualToString:@"fullName"]) {
            NSArray *affectingKeys = @[@"name", @"nick"];
            keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
        }
        return keyPaths;
    }

看看fullName監(jiān)聽效果:

fullName監(jiān)聽效果

  • 集合屬性的監(jiān)聽
    對(duì)于不可變集合屬性,我們更多的是把它當(dāng)成一個(gè)整體來(lái)監(jiān)聽,而無(wú)法去監(jiān)聽集合中的某個(gè)元素的變化;對(duì)于可變集合屬性,實(shí)際上也是當(dāng)成一個(gè)整體,去監(jiān)聽它整體的變化,如添刪除替換元素。具體案例如下:
    可變數(shù)組添加監(jiān)聽

    如果想監(jiān)聽集合中數(shù)據(jù)的變化,如添加、刪除替換元素該如何處理呢?向可變數(shù)組中添加元素,這種處理方式沒有效果。見下圖:
    數(shù)組中添加元素案例

    KVO鍵值監(jiān)聽實(shí)現(xiàn)的基礎(chǔ)是KVC。我們以數(shù)組為例,在我們的Person類中有一個(gè)dateArray數(shù)組屬性,如果我們希望響應(yīng)dateArray所有的方法,則需要實(shí)現(xiàn)以下方法:
    官方說(shuō)明

    所以對(duì)于可變集合,我們不使用valueForKey:來(lái)獲取對(duì)象,而是使用以下方法:
    案例分析

    由打印信息可以發(fā)現(xiàn)kind字段的值發(fā)生了而變化,輸出值為23、4。這是因?yàn)椋?code>KVO機(jī)制能在集合改變的時(shí)候把詳細(xì)的變化放進(jìn)change字典中。

補(bǔ)充:集合(Set)也有一套對(duì)應(yīng)的方法來(lái)實(shí)現(xiàn)集合代理對(duì)象,包括無(wú)序集合有序集合;而字典則沒有,對(duì)于字典屬性的監(jiān)聽,還是只能作為一個(gè)整體來(lái)處理。

如果我們想到手動(dòng)控制集合屬性消息的發(fā)送,則可以使用上面提到的幾個(gè)方法,即:

    -willChange:valuesAtIndexes:forKey:
    -didChange:valuesAtIndexes:forKey:

    或

    -willChangeValueForKey:withSetMutation:usingObjects:
    -didChangeValueForKey:withSetMutation:usingObjects:

注意:先要把自動(dòng)通知關(guān)閉,否則每次改變KVO都會(huì)被發(fā)送兩次。

  • 變化字典
    觀察者對(duì)象必須實(shí)現(xiàn) -observeValueForKeyPath:ofObject:change:context:方法,來(lái)對(duì)屬性修改通知做相應(yīng)的處理。這個(gè)方法的聲明如下:
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context

第三個(gè)參數(shù),通常稱之為變化字典(Change Dictionary),它記錄了被監(jiān)聽屬性的變化情況。這個(gè)字典中包含的值,會(huì)根據(jù)我們?cè)谔砑佑^察者時(shí)設(shè)置的options參數(shù)的不同而有所不同,它包含了屬性被修改的一些信息。我們可以通過(guò)以下key來(lái)獲取我們想要的信息:


typedef NSString * NSKeyValueChangeKey NS_STRING_ENUM;

/* 
Keys for entries in change dictionaries. See the comments for -observeValueForKeyPath:ofObject:change:context: for more information.
*/
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeKindKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNewKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeOldKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeIndexesKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNotificationIsPriorKey API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

其中,NSKeyValueChangeKindKey的值取自于NSKeyValueChange,它的值是由以下枚舉定義的:

enum {
     // 設(shè)置一個(gè)新值。被監(jiān)聽的屬性可以是一個(gè)對(duì)象,也可以是一對(duì)一關(guān)系的屬性或一對(duì)多關(guān)系的屬性。
     NSKeyValueChangeSetting = 1,
     // 表示一個(gè)對(duì)象被插入到一對(duì)多關(guān)系的屬性。
     NSKeyValueChangeInsertion = 2,
     // 表示一個(gè)對(duì)象被從一對(duì)多關(guān)系的屬性中移除。
     NSKeyValueChangeRemoval = 3,
     // 表示一個(gè)對(duì)象在一對(duì)多的關(guān)系的屬性中被替換
     NSKeyValueChangeReplacement = 4
 };
 typedef NSUInteger NSKeyValueChange;

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

在上面了解了NSKeyValueObserving所提供的功能后,我們?cè)賮?lái)看看KVO的實(shí)現(xiàn)機(jī)制,以便更深入地的理解KVO。KVO沒有開源,所以我們無(wú)法從源代碼的層面來(lái)分析它的實(shí)現(xiàn)。那么我們還是先查看官方的描述:

官方說(shuō)明

翻譯過(guò)來(lái):自動(dòng)鍵值觀察是使用一種稱為isa-swizzling的技術(shù)實(shí)現(xiàn)的。isa指針指向維護(hù)調(diào)度表的對(duì)象的類。 該調(diào)度表主要包含指向類實(shí)現(xiàn)的方法的指針,以及其他數(shù)據(jù)。當(dāng)觀察者為對(duì)象的屬性注冊(cè)時(shí),被觀察對(duì)象的isa指針被修改,指向中間類而不是真正的類。 因此,isa指針的值不一定反映實(shí)例的實(shí)際類。
所以我們就提出了幾個(gè)疑問(wèn),這個(gè)isa指向的中間類是什么?kvo觀察的是setter方法,setter方法做了什么,調(diào)用的又是誰(shuí)的setter方法?移除監(jiān)聽后這個(gè)中間類是否銷毀呢?帶著疑問(wèn)我們繼續(xù)往下走。

  • 尋找中間類NSKVONotifying_LGPerson
    首先我們通過(guò)設(shè)置斷點(diǎn),來(lái)逐步跟蹤person對(duì)象isa指針?biāo)赶虻念?,見下圖:
    跟蹤isa指向

    在添加監(jiān)聽之前,person對(duì)象對(duì)應(yīng)的類是LGPerson,添加過(guò)監(jiān)聽之后,person對(duì)象isa指向的類是NSKVONotifying_LGPerson。這個(gè)類應(yīng)該就是官網(wǎng)中說(shuō)到的中間類。

那么這個(gè)中間類是何時(shí)創(chuàng)建的呢?我們?cè)谡{(diào)用addObserver:forKeyPath:options:context:方法之前,獲取NSKVONotifying_LGPerson這個(gè)類,發(fā)現(xiàn)這個(gè)類并不存在。見下圖:

案例分析

說(shuō)明這個(gè)類應(yīng)該是通過(guò)runtime在運(yùn)行時(shí)動(dòng)態(tài)生成的。

  • NSKVONotifying_LGPersonLGPerson的關(guān)系
    通過(guò)lldb調(diào)試,打印NSKVONotifying_LGPerson類的地址,獲取其內(nèi)存空間,發(fā)現(xiàn)NSKVONotifying_LGPerson的父類是LGPerson類。

    案例分析

    所以,NSKVONotifying_LGPersonLGPerson的子類。(如果不明白為什么看內(nèi)存就知道是父類的話建議區(qū)看看類的內(nèi)存結(jié)構(gòu))

  • 中間類提供的方法
    提供下面一個(gè)輔助方法,用來(lái)獲取類中的方法列表。如下:

    #pragma mark **- 遍歷方法-ivar-property**
    - (void)printClassAllMethod:(Class)cls{
        unsigned int count = 0;
        Method *methodList = class_copyMethodList(cls, &count);
        for (int i = 0; i<count; i++) {
            Method method = methodList[i];
            SEL sel = method_getName(method);
            IMP imp = class_getMethodImplementation(cls, sel);
            NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
        }
        free(methodList);
    }

在調(diào)用addObserver:forKeyPath:options:context:方法之后,調(diào)用該輔助方法,查看NSKVONotifying_LGPerson類中有哪些功能。見下圖:

查看子類的方法

發(fā)現(xiàn)中間類重寫了父類的四個(gè)方法。分別是setNickNameclass、dealloc_isKVOA。

  • 對(duì)象的isa何時(shí)修復(fù)
    通過(guò)上面的分析,我們發(fā)現(xiàn)在調(diào)用addObserver:forKeyPath:options:context:方法之后,對(duì)象的isa指向了一個(gè)中間類,那么isa和在重新執(zhí)行LGPerson類呢?

  • (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;方法,也就是移除監(jiān)聽的時(shí)候。我們來(lái)驗(yàn)證一下:

    驗(yàn)證isa修復(fù)

    在調(diào)用- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;方法之后,對(duì)象的isa指針重新指向了LGPerson類。

注意:在完成觀察者的銷毀之后,這個(gè)中間類依然存在,并沒有被銷毀。(為下次使用做準(zhǔn)備,性能的考慮,避免重復(fù)創(chuàng)建),請(qǐng)繼續(xù)往下看。

中間類沒有銷毀

  • 中間類中setter方法的作用
    這里setter方法做了什么,監(jiān)聽的是屬性還是成員變量呢?我們做個(gè)監(jiān)聽分別采用操作屬性和訪問(wèn)成員變量的方式,分別變更nickNamename,見下圖:
    驗(yàn)證中間類的setter方法

    說(shuō)明KVO實(shí)際是通過(guò)setter方法監(jiān)聽的是屬性。我們可以通過(guò)監(jiān)聽nickName成員變量來(lái)分析底層調(diào)用過(guò)程。見下圖:
    堆棧分析

    通過(guò)堆棧我們可以發(fā)現(xiàn),在調(diào)用setNickName方法是,底層實(shí)際是調(diào)用了下面的流程:
  • Foundation _NSSetObjectValueAndNotify
  • Foundation -[NSObject(NSKeyValueObservingPrivate)
  • _changeValueForKey:key:key:usingBlock:]
  • Foundation -[NSObject(NSKeyValueObservingPrivate)
  • _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]

總結(jié)

Objective-C基于強(qiáng)大的run time機(jī)制來(lái)實(shí)現(xiàn)KVO。當(dāng)?shù)谝淮斡^察某個(gè)對(duì)象的屬性時(shí),run time會(huì)創(chuàng)建一個(gè)新的繼承自這個(gè)對(duì)象的classsubclass。在這個(gè)新的subclass中,它會(huì)重寫所有被觀察的keysetter方法,然后將對(duì)象的isa指針指向新創(chuàng)建的class(這個(gè)指針告訴Objective-C運(yùn)行時(shí)某個(gè)對(duì)象到底是什么類型的)。所以實(shí)例對(duì)象變成了新的子類的實(shí)例。完成以上操作后,通過(guò)調(diào)用setter方法進(jìn)行相關(guān)屬性的變化時(shí),操作的就是這個(gè)中間的子類。但是底層依然會(huì)將對(duì)中間類操作的狀態(tài),同步到原對(duì)象中。在進(jìn)行監(jiān)聽移除后,對(duì)象的isa回復(fù)到原來(lái)的類上,且中間類沒有跟著被移除

最后編輯于
?著作權(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)容