420,Swift3、4中的@objc、@objcMembers和dynamic(面試點(diǎn):@objc用來(lái)給object-c的運(yùn)行時(shí)(類,協(xié)議,屬性,方法) swift4 繼承自NSObject的...

背景

Objective-C 對(duì)象是基于運(yùn)行時(shí)的,方法或?qū)傩允褂脛?dòng)態(tài)派發(fā) ,在運(yùn)行調(diào)用時(shí)再?zèng)Q定實(shí)際調(diào)用的具體實(shí)現(xiàn)。而 Swift 為了追求性能,如果沒(méi)有特殊需要的話,是不會(huì)在運(yùn)行時(shí)再來(lái)決定這些的。也就是說(shuō),Swift 類型的成員或者方法在編譯時(shí)就已經(jīng)決定,而運(yùn)行時(shí)便不再需要經(jīng)過(guò)一次查找,而可以直接使用。

Objective-C 中所有類都繼承自NSObject,Swift 中的類如果要供 Objective-C 調(diào)用,必須也繼承自NSObject。

@objc

@objc修飾符的根本目的是用來(lái)暴露接口給 Objective-C 的運(yùn)行時(shí)(類、協(xié)議、屬性和方法等)

添加@objc修飾符并不意味著這個(gè)方法或者屬性會(huì)采用 Objective-C 的方式變成動(dòng)態(tài)派發(fā),Swift 依然可能會(huì)將其優(yōu)化為靜態(tài)調(diào)用

@objc 修飾符的隱式添加:

Swift 3 中繼承自NSObject的類,不需要手動(dòng)添加@objc,編譯器會(huì)給所有的非private的類和成員加上@objc,private接口想要暴露給 Objective-C 需要@objc的修飾

button.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside)
@objc private func backButtonTapped() { }
func backButtonTapped() { }

Swift 4 中繼承自NSObject的類的隱式@objc自動(dòng)添加,只會(huì)發(fā)生在以下四種情況:

1.重寫(xiě)了父類的 Objective-C 方法
2.實(shí)現(xiàn)了一個(gè) Objective-C 的協(xié)議
3.@IBAction或@IBOutlet關(guān)鍵字的修飾
4.@NSManaged關(guān)鍵字的修飾

@NSManaged的定義

Core Data 提供了基本存儲(chǔ)和實(shí)現(xiàn)NSManagedObject子類的一組屬性。在與Core Data 模型中管理對(duì)象子類相關(guān)的特性或者關(guān)系的每個(gè)屬性定義之前,將@NSmanaged特性加入。與 Objective-C 里面的 @dynamic特性類似,@NSManaged特性告知 Swift 編譯器,這個(gè)屬性的存儲(chǔ)和實(shí)現(xiàn)將在運(yùn)行時(shí)完成。但是,與@dynamic不同的是,@NSManaged特性僅在 Core Data 支持中可用。

使用@objc可以修改 Swift 接口暴露到 Objective-C 后的名字

@objc(Squirrel)
class Белка: NSObject {
    @objc(color)
    var цвет: Цвет = .Красный
 
    @objc(hideNuts:inTree:)
    func прячьОрехи(количество: Int, вДереве дерево: Дерево) { }
}

@objcMembers

使用@objcMembers關(guān)鍵字,將類中的所有方法暴露給Objc (效果等同于為所有方法加上@objc)。

為什么要用這個(gè)關(guān)鍵字呢?

@objcMembers 在Swift 4中繼承 NSObject 的 swift class 不再默認(rèn)全部 bridge 到 OC,如果我們想要使用的話我們就需要在class前面加上@objcMembers 這么一個(gè)關(guān)鍵字。
引用: 在 swift 3 中除了手動(dòng)添加 @objc 聲明函數(shù)支持 OC 調(diào)用還有另外一種方式:繼承 NSObject。
class 繼承了 NSObject 后,編譯器就會(huì)默認(rèn)給這個(gè)類中的所有函數(shù)都標(biāo)記為 @objc ,支持 OC 調(diào)用。
蘋(píng)果在Swift 4 中蘋(píng)果修改了自動(dòng)添加 @objc 的邏輯: 一個(gè)繼承 NSObject 的 swift 類不再默認(rèn)給所有函數(shù)添加 @objc。
只在實(shí)現(xiàn) OC 接口和重寫(xiě) OC 方法時(shí)才自動(dòng)給函數(shù)添加 @objc 標(biāo)識(shí)。
Swift4 后繼承自NSObject的類不再隱式添加@objc關(guān)鍵字,但在某些情況下非常依賴 Objective-C 的運(yùn)行時(shí)(如 XCTest),所以在 Swift4 中提供了@objcMembers關(guān)鍵字,對(duì)類和子類、擴(kuò)展和子類擴(kuò)展重新啟用@objc推斷。

@objcMembers
class MyClass : NSObject {
  func foo() { }             // implicitly @objc
 
  func bar() -> (Int, Int)   // not @objc, because tuple returns
      // aren't representable in Objective-C
}
 
extension MyClass {
  func baz() { }   // implicitly @objc
}
 
class MySubClass : MyClass {
  func wibble() { }   // implicitly @objc
}
 
extension MySubClass {
  func wobble() { }   // implicitly @objc
}

使用@objc和@nonobjc可以指定開(kāi)啟或關(guān)閉某一extension中的所有方法的@objc推斷。

class SwiftClass { }
 
@objc extension SwiftClass {
  func foo() { }            // implicitly @objc
  func bar() -> (Int, Int)  // error: tuple type (Int, Int) not
      // expressible in @objc. add @nonobjc or move this method to fix the issue
}
 
@objcMembers
class MyClass : NSObject {
  func wibble() { }    // implicitly @objc
}
 
@nonobjc extension MyClass {
  func wobble() { }    // not @objc, despite @objcMembers
}

dynamic

當(dāng)前 Swift 的動(dòng)態(tài)性依賴于 Objective-C,Swift3 中dynamic就隱式包含了@objc的意思,但考慮到以后版本的 Swift 語(yǔ)言和運(yùn)行時(shí)將會(huì)自支持dynamic而不再依賴于 Objective-C,所以在 Swift4 中將dynamic和@objc含義進(jìn)行了抽離

class MyClass {
  dynamic func foo() { }       // error: 'dynamic' method must be '@objc'
  @objc dynamic func bar() { } // okay

dynamic關(guān)鍵字

如果您有過(guò)OC的開(kāi)發(fā)經(jīng)驗(yàn),那一定會(huì)對(duì)OC中@dynamic關(guān)鍵字比較熟悉,它告訴編譯器不要為屬性合成getter和setter方法。

Swift中也有dynamic關(guān)鍵字,它可以用于修飾變量或函數(shù),它的意思也與OC完全不同。它告訴編譯器使用動(dòng)態(tài)分發(fā)而不是靜態(tài)分發(fā)。OC區(qū)別于其他語(yǔ)言的一個(gè)特點(diǎn)在于它的動(dòng)態(tài)性,任何方法調(diào)用實(shí)際上都是消息分發(fā),而Swift則盡可能做到靜態(tài)分發(fā)。

因此,標(biāo)記為dynamic的變量/函數(shù)會(huì)隱式的加上@objc關(guān)鍵字,它會(huì)使用OC的runtime機(jī)制。

雖然靜態(tài)分發(fā)在效率上可能更好,不過(guò)一些app分析統(tǒng)計(jì)的庫(kù)需要依賴動(dòng)態(tài)分發(fā)的特性,動(dòng)態(tài)的添加一些統(tǒng)計(jì)代碼,這一點(diǎn)在Swift的靜態(tài)分發(fā)機(jī)制下很難完成。這種情況下,雖然使用dynamic關(guān)鍵字會(huì)犧牲因?yàn)槭褂渺o態(tài)分發(fā)而獲得的一些性能優(yōu)化,但也依然是值得的。

class Kraken {
    dynamic var imADynamicallyDispatchedString: String
 
    dynamic func imADynamicallyDispatchedFunction() {
        //Hooray for dynamic dispatch!
    }
}

使用動(dòng)態(tài)分發(fā),您可以更好的與OC中runtime的一些特性(如CoreData,KVC/KVO)進(jìn)行交互,不過(guò)如果您不能確定變量或函數(shù)會(huì)被動(dòng)態(tài)的修改、添加或使用了Method-Swizzle,那么就不應(yīng)該使用dynamic關(guān)鍵字,否則有可能程序崩潰。

注意:

使用dynamic關(guān)鍵字標(biāo)記屬性,使屬性啟用Objc的動(dòng)態(tài)轉(zhuǎn)發(fā)功能;

dynamic只用于類,不能用于結(jié)構(gòu)體和枚舉,因?yàn)樗鼈儧](méi)有繼承機(jī)制,而Objc的動(dòng)態(tài)轉(zhuǎn)發(fā)就是根據(jù)繼承關(guān)系來(lái)實(shí)現(xiàn)轉(zhuǎn)發(fā)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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