OC和swift的屬性觀察器的底層實(shí)現(xiàn)

背景

屬性觀察器這個(gè)概念在swift里算是基礎(chǔ)常識了,然而,最近做項(xiàng)目的時(shí)候,遇到了屬性觀察器不生效的問題,遂探究了一番,今天與大家分享一下。

現(xiàn)象

OBJC設(shè)置swift的屬性

如果swift里有這樣一個(gè)屬性

class ClassA: NSObject {
    @objc var canIncreaseNums: Int = 0 {
        didSet {
            print("increase")
        }
    }
}

同時(shí)呢我們在OC的代碼里有

@implementation ClassB
+ (void)test {
   [[[ClassA alloc] init] setCanIncreaseNums:2];
}
@end

那么我們執(zhí)行下邊這行代碼的時(shí)候會(huì)發(fā)生什么呢?

ClassB.test()

答案是會(huì)通過OBJC的ClassA類調(diào)用swift的ClassA類,系統(tǒng)的函數(shù)調(diào)用棧如下

0. ClassA.canIncreaseNums.didset
1. ClassA.canIncreaseNums.setter
2. @objc ClassA.canIncreaseNums.setter

我們再看另外一種情況

OBJC設(shè)置OBJC的屬性

如果OBJC里有這樣一個(gè)屬性

//.h文件中
@interface ClassC : NSObject
@property (assign, readwrite) NSInteger ocNumPro;
@end

//.m文件中
@implementation ClassC
@synthesize ocNumPro = _ocNumPro;
- (NSInteger)ocNumPro {
    return 2;
}

- (void)setOcNumPro:(NSInteger)ocNumPro {
    _ocNumPro = ocNumPro;
    printf(@"oc set");
}
@end

同樣我們也在test函數(shù)中設(shè)置一下這個(gè)屬性

+ (void)test {
    [[ClassC new] setOcNumPro:2];
}

那么,當(dāng)我們執(zhí)行以下代碼的時(shí)候

[ClassC test];

你會(huì)發(fā)現(xiàn)堆棧里只有

[ClassC setOcNumPro:]

這個(gè)函數(shù)的調(diào)用。

swift設(shè)置swift屬性

假設(shè)我們?nèi)缜闆r一一樣在swift類中有一個(gè)ClassA的類
然后我們直接在swift文件中執(zhí)行以下代碼

ClassA().canIncreaseNums = 2

你會(huì)發(fā)現(xiàn)堆棧只有兩個(gè)函數(shù)調(diào)用

ClassA.canIncreaseNums.didset
ClassA.canIncreaseNums.setter

那么,這三者有什么不同呢。

原理

我們知道對于自定義的存儲(chǔ)屬性來說,添加屬性觀察器之后就可以監(jiān)控和響應(yīng)屬性值的變化,每次屬性被設(shè)置值的時(shí)候都會(huì)調(diào)用屬性觀察器,即使新值和當(dāng)前值相同的時(shí)候也不例外。
那么,按照這個(gè)說法,我們的情況三是符合理論的,那么情況一為什么會(huì)多走一次函數(shù)調(diào)用呢
我們的情況3中 執(zhí)行函數(shù)調(diào)用的地方是swift,而情況1中是在oc。那么我們不妨大膽猜測一下,情況1中額外的調(diào)用的原因是 系統(tǒng)生成了一個(gè)@objc ClassA 這樣的一個(gè)類 我們給類A加上@OBJC修飾符變成

@objc(ClassF) 
class ClassA: NSObject {
    @objc var canIncreaseNums: Int = 0 {
        didSet {
            print("increase")
        }
    }
}

這時(shí)你會(huì)發(fā)現(xiàn)OC文件中只能這么寫了

+ (void)test {
    [[[ClassF alloc] init] setCanIncreaseNums:2];
}

看來我們的猜想是對的。
那么問題來了,為什么要生成這個(gè)同名的OBJC類呢
我們知道OBJC是有一個(gè)運(yùn)行時(shí)的概念的,而swift是沒有的,所以,假設(shè)我們想要利用運(yùn)行時(shí)的技術(shù),比如自省那么不生成這個(gè)同名的OBJC的類的話,就沒有辦法實(shí)現(xiàn)了。
此外關(guān)于@objc修飾符還有以下幾個(gè)注意事項(xiàng)

  1. @objc 關(guān)于類聲明意味著帶注釋的類將暴露給Objective-C運(yùn)行時(shí),例如您可以嘗試從純Swift類(而不是NSObject的子類)中省略注釋,然后您無法在Objective-C代碼中使用此類(它不會(huì)出現(xiàn)在Objective-C使用的生成頭文件中)。
  2. 任何繼承 NSObject 或其任何子類的類都將自動(dòng)(隱式)標(biāo)記為 @objc . 因此,將 UIViewController 的子類明確標(biāo)記為 @objc 是沒有意義的,因?yàn)镾wift編譯器會(huì)為您執(zhí)行此操作。

總結(jié)

屬性觀察器的使用,意外的發(fā)現(xiàn)了底層我們平時(shí)不曾注意的一些細(xì)節(jié)。多積累,多思考。

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

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

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