Swift與Objective-C的兼容「黑魔法」:@objc和Dynamic

2021-01-12 CSDN技術社區

雖然說Swift語言的初衷是希望能擺脫Objective-C的沉重的歷史包袱和約束,但是不可否認的是經過了二十多年的洗禮,Cocoa框架早就烙上了不可磨滅的Objective-C的印記。無數的第三方庫是用Objective-C寫成的,這些積累無論是誰都不能小覷。因此,在最初的版本中,Swift不得不考慮與Objective-C的兼容。


Apple採取的做法是允許我們在同一個項目中同時使用Swift和Objective-C來進行開發。其實一個項目中的Objective-C文件和Swift文件是處於兩個不同世界中的,為了讓它們能相互聯通,我們需要添加一些橋梁。

首先通過添加{product-module-name}-Bridging-Header.h文件,並在其中填寫想要使用的頭文件名稱,我們就可以很容易地在Swift中使用Objective-C代碼了。Xcode為了簡化這個設定,甚至在Swift項目中第一次導入Objective-C文件時會主動彈框進行詢問是否要自動創建這個文件,可以說是非常方便。

但是如果想要在Objective-C中使用Swift的類型的時候,事情就複雜一些。如果是來自外部的框架,那麼這個框架與Objective-C項目肯定不是處在同一個target中的,我們需要對外部的Swift module進行導入。這個其實和使用Objective-C的原來的Framework是一樣的,對於一個項目來說,外界框架是由Swift寫的還是Objective-C寫的,兩者並沒有太大區別。我們通過使用2013年新引入的@import來引入module:

@import MySwiftKit;

之後就可以正常使用這個Swift寫的框架了。

如果想要在Objective-C裡使用的是同一個項目中的Swift的源文件的話,可以直接導入自動生成的頭文件{product-module-name}-Swift.h來完成。比如項目的target叫做MyApp的話,我們就需要在Objective-C文件中寫:

#import "MyApp-Swift.h"

但這只是故事的開始。Objective-C和Swift在底層使用的是兩套完全不同的機制,Cocoa中的Objective-C對象是基於運行時的,它從骨子裡遵循了KVC(Key-Value Coding,通過類似字典的方式存儲對象信息)以及動態派發(Dynamic Dispatch,在運行調用時再決定實際調用的具體實現)。而Swift為了追求性能,如果沒有特殊需要的話,是不會在運行時再來決定這些的。也就是說,Swift類型的成員或者方法在編譯時就已經決定,而運行時便不再需要經過一次查找,而可以直接使用。

顯而易見,這帶來的問題是如果我們要使用Objective-C的代碼或者特性來調用純Swift的類型時候,我們會因為找不到所需要的這些運行時信息,而導致失敗。解決起來也很簡單,在Swift類型文件中,我們可以將需要暴露給Objective-C使用的任何地方(包括類,屬性和方法等)的聲明前面加上@objc修飾符。注意這個步驟只需要對那些不是繼承自NSObject的類型進行,如果你用Swift寫的class是繼承自NSObject的話,Swift會默認自動為所有的非private的類和成員加上@objc。這就是說,對一個NSObject的子類,你只需要導入相應的頭文件就可以在Objective-C裡使用這個類了。

@objc修飾符的另一個作用是為Objective-C側重新聲明方法或者變量的名字。雖然絕大部分時候自動轉換的方法名已經足夠好用(比如會將Swift中類似init(name: String) 的方法轉換成-initWithName:(NSString *)name這樣),但是有時候我們還是期望Objective-C裡使用和Swift中不一樣的方法名或者類的名字,比如Swift裡這樣的一個類:

class 我的類 {    func 打招呼(名字: String) {        println("哈嘍,\(名字)")    }}我的類().打招呼("小明")

Objective-C的話是無法使用中文來進行調用的,因此我們必須使用@objc將其轉為ASCII才能在Objective-C裡訪問:

@objc(MyClass)class 我的類 {    @objc(greeting:)    func 打招呼(名字: String) {        println("哈嘍,\(名字)")    }}

這樣,我們在Objective-C裡就能調用 [[MyClass new] greeting:@"XiaoMing"] 這樣的代碼了(雖然比起原來一點都不好玩了)。另外,正如上面所說的以及在Selector一節中所提到的,即使是NSObject的子類,Swift也不會在被標記為private的方法或成員上自動加@objc。如果我們需要使用這些內容的動態特性的話,我們需要手動給它們加上@objc修飾。

添加@objc修飾符並不意味著這個方法或者屬性會變成動態派發,Swift依然可能會將其優化為靜態調用。如果你需要和Objective-C裡動態調用時相同的運行時特性的話,你需要使用的修飾符是dynamic。一般情況下在做App開發時應該用不上,但是在施展一些像動態替換方法或者運行時再決定實現這樣的 "黑魔法" 的時候,我們就需要用到dynamic修飾符了。在之後的KVO一節中,我們還會提到一個關於使用dynamic的實例。

作者:王巍(@onevcat),iOS和Unity3D開發者。

本文轉載自:Swifter

點擊連結進入Swift技術社區,了解更多Swift開發的技術熱點內容!

CSDN JOB移動工程師專場招聘,直擊20家企業高薪職位-->http://job.csdn.net/event/mobdev.html

相關焦點

  • Swift和Objective-C混編
    }Swift閉包和Objective-C block是兼容的,可以將Swift閉包傳遞給接受block的Objective-C方法。Swift閉包和函數是相同的類型,所以也可以給Objective-C的block傳遞一個Swift的函數名字。閉包和block有相似的值捕獲功能,但是閉包有一點不一樣:變量默認是可以修改的。
  • 雜談App Store 之 HIG 與 Objective-C
    沒經驗的團隊,經常扯了半天,畫了一堆線框圖,發現有系統控制項和術語。有經驗的團隊,簡單溝通一下,同樣的時間demo都做出來了。HIG裡面介紹了iOS的常用的控制項(UIControl),iOS系統原生的應用多數是用內置的UIControl製作的。不要重複造輪子,浪費生命很沒意思。很多人喜歡談創新(我最怕的詞是「顛覆式創新」,當年都快聽吐了,創新這個詞就這麼廉價麼?)
  • Objective-C 和 Swift 面試題
    類(class)和結構體(struct)有什麼區別?Swift 中,類是引用類型,結構體是值類型。值類型在傳遞和賦值時將進行複製,而引用類型則只會使用引用對象的一個"指向"。所以他們兩者之間的區別就是兩個類型的區別。
  • Swift學習: 從Objective-C到Swift
    for index in stride(from: 1, through: 5, by: 2) { print(index)}然後對字典的遍歷也增強了.在Objective-c的快速枚舉中我們只能對字典的鍵進行枚舉。
  • 來一次有側重點的區分Swift與Objective-C
    一、Objective-C與Swift的異同1.1、swift和OC的共同點:1.2、swift的優點:- swift注重安全,OC注重靈活- swift注重面向協議編程、函數式編程、面向對象編程,OC注重面向對象編程- swift注重值類型,OC注重指針和引用- swift是靜態類型語言,OC是動態類型語言- swift容易閱讀,文件結構和大部分語法簡易化,只有.swift文件,結尾不需要分號- swift中的可選類型,是用於所有數據類型,而不僅僅局限於類。
  • Objective-C的元組實現(JDTuple)
    那勢必能夠提高不少開發的效率,讓OC這門古老的語言也swift一下。元組(tuple)是一種數據結構,它本來是一種關係型資料庫的概念,用來描述一張表中的一條數據。例如:那麼一行數據就是一個元組,即: tuple1 = (阿強,18,戰士);tuple2 = (阿珍,28,法師)。這個概念後來也被使用在代碼中,成為一種數據封裝的工具。
  • Objective-C 與 Runtime:為什麼是這樣?
    來自:springox的博客連結:http://springox.w18.net/2015/09/03/objectivecruntime
  • 程式語言:Objective-C和C++有何不同?
    那麼同樣是「C」,Objective-C和C++之間有何不同?Obj-c(圖:ios.25pp.com)  Objective-C,通常寫作ObjC和較少用的Objective C或Obj-C,是擴充C的面向對象程式語言。
  • SwiftUI 的 DSL 語法分析
    在中文中,Property 和 Attribute 都被翻譯成屬性了。但兩者在 Swift 中是不同的概念。Swift 中有各種 Attribute,比如@dynamicCallable@dynamicMemberLookup@available@objcSwift 的 Attribute 語法可以放到類型定義或者函數定義的前面,是對類型和函數的一種標記。
  • 這些年我們愛著的 Objective-C
    成長1983 年,Brad Cox和Tom Love成立了Stepstone公司,並發布了第一個Objective-C實現。據Brad Cox回憶,最初的實現就是個簡單的預處理器。他們使用sed和awk等工具把Objective-C直接翻譯為C語言。可C語言並不原生支持Objective-C的動態語義,例如dynamic dispatch。
  • Swift 開源了,Objective-C還能走多遠?
    也以其語法簡潔、安全為廣大開發者使用,在這一年中,國外的Swift iOS書籍、教程和培訓,無一例外已全部採用 Swift 來編寫。在國內,Swift 書籍、培訓也正在興起,公司也在接納這門新的程式語言,比如優才學院還推出了iOS全棧工程師培訓課程 。如今一年過去,蘋果又做出驚人之舉。
  • 這些年我們愛著的Objective-C
    成長1983 年,Brad Cox和Tom Love成立了Stepstone公司,並發布了第一個Objective-C實現。據Brad Cox回憶,最初的實現就是個簡單的預處理器。他們使用sed和awk等工具把Objective-C直接翻譯為C語言。可C語言並不原生支持Objective-C的動態語義,例如dynamic dispatch。
  • 通過LLVM 在 Android 上運行 Swift 代碼
    我們現在需要手動編譯和連接一個簡單的 Swift "Hello world" :// hello.swiftprint("Hello, world!");構建對象文件:$ $SDK/usr/bin/swiftc -emit-object hello.swifthello.o 裡面到底有什麼:$ nm hello_swift.o                 U __TFSSCfMSSFT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1
  • 從Mach-O角度談談Swift和OC的存儲差異
    歸根到底還是由於Mach-O文件存儲了類和函數的信息。在Mach-O中,所有的類都存儲到__objc_classlist這個section中。通過 __objc_classlist中的地址,我們能找到每個類的詳細信息。本文以arm64架構為例,在找到0x11820文件偏移後,我們很容易通過結構體結構套取到類的信息。
  • Objective-C 裡的 eval
    計算器程序是 swift 編寫的。比賽給了 x64 模擬器版和 arm64(這是一個小錯誤,後面文章會解釋)的真機版二進位文件。可執行文件沒有去除符號,除了 swift 語言生成的 mangled 符號很冗長,閱讀起來沒有太大問題。核心代碼只有寥寥數行。
  • 在 Flutter 中玩轉 Objective-C Block
    很多 Objective-C 接口的參數和返回值是 Block,所以這就需要支持用 Dart 語言創建和調用 Objective-C Block。Dart 調用 Objective-C 帶 Block 的 APIDart 語言支持協程,這樣就無需傳遞閉包來作為異步調用的回調。