OC 和 Swift 運(yùn)行時(shí)簡(jiǎn)介
Objective-C 運(yùn)行時(shí)
- 動(dòng)態(tài)類型(dynamic typing)
- 動(dòng)態(tài)綁定(dynamic binding)
-
動(dòng)態(tài)裝載(dynamic loading)
01
派發(fā)方式
- 直接派發(fā) (Direct Dispatch)
- 函數(shù)表派發(fā) (Table Dispatch )
- 消息機(jī)制派發(fā) (Message Dispatch )
直接派發(fā)
- 直接派發(fā)是最快的, 不止是因?yàn)樾枰{(diào)用的指令集會(huì)更少, 并且編譯器還能夠有很大的優(yōu)化空間, 例如函數(shù)內(nèi)聯(lián)等, 直接派發(fā)也有人稱為靜態(tài)調(diào)用氢架。
- 然而, 對(duì)于編程來說直接調(diào)用也是最大的局限, 而且因?yàn)槿狈?dòng)態(tài)性所以沒辦法支持繼承和多 態(tài)。
函數(shù)表派發(fā)
-
函數(shù)表派發(fā)是編譯型語言實(shí)現(xiàn)動(dòng)態(tài)行為最常見的實(shí)現(xiàn)方式. 函數(shù)表使用了一個(gè)數(shù)組來存儲(chǔ)類聲明的 每一個(gè)函數(shù)的指針. 大部分語言把這個(gè)稱為 “virtual table”(虛函數(shù)表), Swift 里稱為 “witness table”. 每一個(gè)類都會(huì)維護(hù)一個(gè)函數(shù)表, 里面記錄著類所有的函數(shù), 如果父類函數(shù)被 override 的 話, 表里面只會(huì)保存被 override 之后的函數(shù). 一個(gè)子類新添加的函數(shù), 都會(huì)被插入到這個(gè)數(shù)組的最 后. 運(yùn)行時(shí)會(huì)根據(jù)這一個(gè)表去決定實(shí)際要被調(diào)用的函數(shù).
03 查表是一種簡(jiǎn)單, 易實(shí)現(xiàn), 而且性能可預(yù)知的方式. 然而, 這種派發(fā)方式比起直接派發(fā)還是慢一點(diǎn). 從字節(jié)碼角度來看, 多了兩次讀和一次跳轉(zhuǎn), 由此帶來了性能的損耗. 另一個(gè)慢的原因在于編譯器可能會(huì)由于函數(shù)內(nèi)執(zhí)行的任務(wù)導(dǎo)致無法 優(yōu)化. (如果函數(shù)帶有副作用的話)
這種基于數(shù)組的實(shí)現(xiàn), 缺陷在于函數(shù)表無法拓展. 子類會(huì)在虛數(shù)函數(shù)表的最后插入新的函數(shù), 沒有位置可以讓 extension 安全地插入函數(shù).
消息機(jī)制派發(fā)
-
消息機(jī)制是調(diào)用函數(shù)最動(dòng)態(tài)的方式. 也是 Cocoa 的基石, 這樣的機(jī)制催生了 KVO, UIAppearence 和 CoreData 等功能. 這種運(yùn)作方式的關(guān)鍵在于開發(fā)者可以在運(yùn)行時(shí)改變函數(shù) 的行為. 不止可以通過 swizzling 來改變, 甚至可以用 isa-swizzling 修改對(duì)象的繼承關(guān)系, 可 以在面向?qū)ο蟮幕A(chǔ)上實(shí)現(xiàn)自定義派發(fā).
04
Swift 運(yùn)行時(shí)
純 Swift 類的函數(shù)調(diào)用已經(jīng)不再是 Objective-c 的運(yùn)行時(shí)發(fā)消息,而是類似 C++ 的 vtable,在編譯時(shí)就確定了 調(diào)用哪個(gè)函數(shù),所以沒法通過 runtime 獲取方法、屬性。
而 Swift 為了兼容 Objective-C鲤脏,凡是繼承自 NSObjec t的類都會(huì)保留其動(dòng)態(tài)性,所以我們能通過 runtime 拿 到他的方法吕朵。這里有一點(diǎn)說明:老版本的 Swift(如2.2)是編譯期隱式的自動(dòng)幫你加上了@objc猎醇,而4.0以后版 本的 Swift 編譯期去掉了隱式特性,必須使用顯式添加努溃。
-
不管是純 Swift 類還是繼承自 NSObject 的類只要在屬性和方法前面添加 @objc 關(guān)鍵字就可以使用 runtime硫嘶。
05 值類型總是會(huì)使用直接派發(fā), 簡(jiǎn)單易懂
而協(xié)議和類的 extension 都會(huì)使用直接派發(fā)
NSObject 的 extension 會(huì)使用消息機(jī)制進(jìn)行派發(fā)
NSObject 聲明作用域里的函數(shù)都會(huì)使用函數(shù)表進(jìn)行派發(fā).
協(xié)議里聲明的, 并且?guī)в心J(rèn)實(shí)現(xiàn)的函數(shù)會(huì)使用函數(shù)表進(jìn)行派發(fā)
Swift 運(yùn)行時(shí)-final @objc
- 可以在標(biāo)記為 final 的同時(shí), 也使用 @objc 來讓函數(shù)可以使用消息機(jī)制派發(fā). 這么做的結(jié)果就 是, 調(diào)用函數(shù)的時(shí)候會(huì)使用直接派發(fā), 但也會(huì)在 Objective-C 的運(yùn)行時(shí)里注冊(cè)相應(yīng)的 selector. 函數(shù)可以響應(yīng) perform(selector:) 以及別的 Objective-C 特性, 但在直接調(diào)用時(shí)又可以有直接 派發(fā)的性能.
- https://github.com/apple/swift/blob/ f4db1dd7a4abba2685247e1a7415d4fcb91f640d/stdlib/public/runtime/SwiftObject.h
橋接
相互調(diào)用
Swift 調(diào)用 OC
- 新建1個(gè)橋接頭文件,文件名格式默認(rèn)為:{targetName}-Bridging-Header.h
在{targetName}-Bridging-Header.h 文件中#import OC需要暴露給Swift的內(nèi)容 - 如果C語言暴露給Swift的函數(shù)名跟Swift中的其他函數(shù)名沖突了
可以在Swift中使用 @_silgen_name 修改C函數(shù)名
// C語言
int sum(int a, int b) {
return a + b;
}
// Swift
@_silgen_name("sum") func swift_sum(_ v1: Int32, _ v2: Int32) -> Int32
print(swift_sum(10, 20)) // 30
print(sum(10, 20)) // 30
OC 調(diào)用 Swift
- Xcode已經(jīng)默認(rèn)生成一個(gè)用于OC調(diào)用Swift的頭文件梧税,文件名格式是: {targetName}-Swift.h
- Swift暴露給OC的類最終繼承自NSObject
//使用@objcMembers修飾類
//代表默認(rèn)所有成員都會(huì)暴露給OC(包括擴(kuò)展中定義的成員)
//最終是否成功暴露沦疾,還需要考慮成員自身的訪問級(jí)別
@objcMembers class Car: NSObject {
var price: Double
var band: String
init(price: Double, band: String) {
self.price = price
self.band = band
}
func run() {
print(price, band, "run")
}
static func run() { print("Car run") }
}
extension Car {
func test() { print(price, band, "test") }
}
- 可以通過@objc 重命名Swift暴露給OC的符號(hào)名(類名、屬性名第队、函數(shù)名等)
@objc(MJCar)
@objcMembers class Car: NSObject {
var price: Double
@objc(name)
var band: String
init(price: Double, band: String) {
self.price = price
self.band = band }
@objc(drive)
func run() { print(price, band, "run") } static func run() { print("Car run") }
}
extension Car {
@objc(exec:v2:)
func test() { print(price, band, "test") } }
MJCar *c = [[MJCar alloc] initWithPrice:10.5 band:@"BMW"]; c.name = @"Bently";
c.price = 108.5;
[c drive]; // 108.5 Bently run
[c exec:10 v2:20]; // 108.5 Bently test [MJCar run]; // Car run
NS_SWIFT_NAME
- 在 Objective-C 中哮塞,重新命名在swift中的名稱。
NS_SWIFT_UNAVAILABLE
- 在 Swift 中不可見凳谦,不能使用忆畅。
采坑指南
Subclass
- 對(duì)于自定義的類而言,Objective-C 的類尸执,不能繼承自 Swift 的類家凯,即要混編的 OC 類不能是 Swift 類的子類缓醋。反過來,需要混編的 Swift 類可以繼承自 OC 的類绊诲。
宏
- 定義一個(gè)常量值送粱,后面可以方便使用;如 #define TOOLBAR_HEIGHT 44;
- 定義一個(gè)不變化的常用值,或者一個(gè)較長(zhǎng)的對(duì)象屬性;如#define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width);
- 定義一個(gè)會(huì)變化的常用變量值驯镊,或者一個(gè)較長(zhǎng)的對(duì)象屬性;如:#define STATUS_BAR_HEIGHT ([UIApplication sharedApplication].statusBarFrame.size.height);
- 定義一個(gè)帶參數(shù)的宏葫督,類似于一個(gè)函數(shù);如#define RGB_COLOR(r,g,b) [UIColor colorWithRed:r/255.f green:g/255.f blue:b/255.f alpha:1.0]
- 第一種的話就比較簡(jiǎn)單竭鞍,可以直接使用let TOOLBAR_HEIGTH:CGFloat = 44來替換就可以了;
- 第二種因?yàn)楹竺娴闹涤肋h(yuǎn)不會(huì)改變板惑,也可以使用let來替換;可以用let SCREEN_WIDTH = UIScreen.mainScreen().bounds.size.width;
- 第三種情況,也就是后面的值會(huì)發(fā)生改變偎快,如狀態(tài)欄高度冯乘,就不能夠使用let來替換了,因?yàn)閘et是定義的常量晒夹,如果使用let裆馒,將 會(huì)導(dǎo)致不能夠獲取正確的值;這里可以使用函數(shù)來獲取:func STATUSBAR_HEIGHT() -> CGFloat { return UIApplication.sharedApplication().statusBarFrame.size.height };使用時(shí)通過函數(shù)STATUSBAR_HEIGTH()獲取狀態(tài)欄高度;
- 第四種,因?yàn)橛休斎雲(yún)?shù)丐怯,所以也只能使用函數(shù)來替換;如:func RGB_COLOR(r:CGFloat, g:CGFloat, b:CGFloat) -> UIColor {return UIColor(red: r, green: g, blue: b, alpha: 1.0)};
Swift 獨(dú)有特性
- Swift 中有許多 OC 沒有的特性喷好,比如,Swift 有元組读跷、為一等公民的函數(shù)梗搅、還有特有的枚舉類 型。所以效览,要使用的混編文件要注意 Swift 獨(dú)有屬性問題无切。
NS_REFINED_FOR_SWIFT
-
Objective-C 的 API 和 Swift 的風(fēng)格相差比較大,Swift 調(diào)用 Objective-C 的API時(shí)可能由于數(shù)據(jù)類型等不 一致導(dǎo)致無法達(dá)到預(yù)期(比如丐枉,Objective-C 里的方法采用了C語言風(fēng)格的多參數(shù)類型;或者 Objective-C 方法返回 NSNotFound哆键,在 Swift 中期望返回 nil)。這時(shí)候就要 NS_REFINED_FOR_SWIFT了瘦锹。
16
知識(shí)點(diǎn)
// MARK: 類似于OC中的 #pragma mark
// MARK: - 類似于OC中的 #pragma mark -
// TODO: 用于標(biāo)記未完成的任務(wù)
// FIXME: 用于標(biāo)記待修復(fù)的問題
條件編譯
func log<T>(_ msg: T,
file: NSString = #file,
line: Int = #line,
fn: String = #function) {
#if DEBUG
let prefix = "\(file.lastPathComponent)_\(line)_\(fn):"
print(prefix, msg)
#endif
}
API可用性
@available(iOS 10, macOS 10.15, ) class Person {}
struct Student {
@available(, unavailable, renamed: "study")
func study_() {}
func study() {}
@available(iOS, deprecated: 11)
@available(macOS, deprecated: 10.12)
func run() {}
}
參考: https://docs.swift.org/swift-book/ReferenceManual/Attributes.html
- 可以通過@objc 重命名Swift暴露給OC的符號(hào)名(類名籍嘹、屬性名、函數(shù)名等)
@objc(MJCar)
@objcMembers class Car: NSObject {
var price: Double
@objc(name)
var band: String
init(price: Double, band: String) {
self.price = price
self.band = band
}
@objc(drive)
func run() { print(price, band, "run") }
static func run() { print("Car run") }
}
extension Car {
@objc(exec:v2:)
func test() { print(price, band, "test") } }
- Swift中依然可以使用選擇器弯院,使用#selector(name)定義一個(gè)選擇器
必須是被@objcMembers或@objc修飾的方法才可以定義選擇器
@objcMembers class Person: NSObject {
func test1(v1: Int) { print("test1") }
func test2(v1: Int, v2: Int) { print("test2(v1:v2:)") }
func test2(_ v1: Double, _ v2: Double) { print("test2(_:_:)") }
func run() {
perform(#selector(test1)) perform(#selector(test1(v1:)))
perform(#selector(test2(v1:v2:)))
perform(#selector(test2(_:_:)))
perform(#selector(test2 as (Double, Double) -> Void))
}
}
關(guān)聯(lián)對(duì)象(Associated Object)
- 在Swift中噩峦,class依然可以使用關(guān)聯(lián)對(duì)象
- 默認(rèn)情況,extension不可以增加存儲(chǔ)屬性
借助關(guān)聯(lián)對(duì)象抽兆,可以實(shí)現(xiàn)類似extension為class增加存儲(chǔ)屬性的效果
class Person {}
extension Person {
private static var AGE_KEY: Void?
var age: Int {
get {
(objc_getAssociatedObject(self, &Self.AGE_KEY) as? Int) ?? 0
}
set {
objc_setAssociatedObject(self, &Self.AGE_KEY,newValue, .OBJC_ASSOCIATION_ASSIGN)
}
}
}
var p = Person()
print(p.age) // 0
p.age = 10
print(p.age) // 10