背景
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ā)。