13.OC和Swift混編

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
02

派發(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ā)

06

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ā)的性能.
07
08

橋接

09
10
11
12

相互調(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
13
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
14
15

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末识补,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辫红,更是在濱河造成了極大的恐慌凭涂,老刑警劉巖祝辣,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異切油,居然都是意外死亡蝙斜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門澎胡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來孕荠,“玉大人,你說我怎么就攤上這事攻谁≈晌椋” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵戚宦,是天一觀的道長(zhǎng)个曙。 經(jīng)常有香客問我,道長(zhǎng)受楼,這世上最難降的妖魔是什么垦搬? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮艳汽,結(jié)果婚禮上猴贰,老公的妹妹穿的比我還像新娘。我一直安慰自己河狐,他們只是感情好米绕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著甚牲,像睡著了一般义郑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丈钙,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天非驮,我揣著相機(jī)與錄音,去河邊找鬼雏赦。 笑死劫笙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的星岗。 我是一名探鬼主播填大,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼俏橘!你這毒婦竟也來了允华?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎靴寂,沒想到半個(gè)月后磷蜀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡百炬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年褐隆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剖踊。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡庶弃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出德澈,到底是詐尸還是另有隱情歇攻,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布圃验,位于F島的核電站掉伏,受9級(jí)特大地震影響缝呕,放射性物質(zhì)發(fā)生泄漏澳窑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一供常、第九天 我趴在偏房一處隱蔽的房頂上張望摊聋。 院中可真熱鬧,春花似錦栈暇、人聲如沸麻裁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煎源。三九已至,卻和暖如春香缺,著一層夾襖步出監(jiān)牢的瞬間手销,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國打工图张, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锋拖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓祸轮,卻偏偏與公主長(zhǎng)得像兽埃,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子适袜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354