Objective-C 與 Swift 混編之路
本文內容基於 Xcode 6.4 和 Swift 1.2
重要資料
為什麼要混編?
- 語言發展趨勢(TIOBE),Swift 排行持續上升, OC 排行呈重力下降
- 專案正常迭代需要
- 很多第三方庫仍然使用 OC 實現
- 專案中原來已經用 OC 實現的模組如果使用 Swift 重寫,代價稍大
- 我們需要在專案中使用 Swift 才能真正碰到問題,解決問題
注:不是為了混編而混編。混編只是在對開發資源、專案管理和技術發展趨勢進行綜合衡量之後做出的比較合理的選擇。
如何開始混編?
步驟
建立工程,Language 選擇 Swift 或 Objective-C 都可以。
建立 Swift 檔案並新增 bridging header 檔案
新增 Swift 檔案時 Xcode 會自動提示你新增 bridging header 檔案,選擇 Yes 即可
進行兩處關鍵設定
這兩處設定 Xcode 預設都會設定好,可以把 Objective-C Bridging Header 和 Objective-C Generated Interface Header Name 改成自己想設定的名字。
至此Swift 與 Objective-C 混編的環境就算配置完成了。
XXX-Bridging-Header.h
如果需要在 Swift 中使用 OC 的程式碼或者庫,只需要在這個檔案中 import
XXX-Swift.h
和 XXX-Bridging-Header.h 不同,XXX-Swift.h 檔案不會出現在專案中,而是由 Xcode 自動生成,你可以在類似如下的路徑下找到相應專案的 XXX-Swift.h 檔案:(PS:演講時沒有寫到PPT裡面,實在抱歉)
/Users/perry/Library/Developer/Xcode/DerivedData/XXX-bhlzdinkujybftbjmgwjwclndmss/Build/Intermediates/XXX.build/Debug-iphonesimulator/XXX.build/Objects-normal /x86_64/XXX-Swift.h
如果需要在 OC 中使用 Swift 程式碼,在使用的檔案中 #import XXX-Swift.h
(PS:其他一些在 OC 中使用 Swift 程式碼的注意事項會在後面詳細說明)
檢視 XXX-Swift.h 檔案中的程式碼:
不難發現這個檔案中的內容其實是將 Swift 中的程式碼轉換成了 OC 的程式碼。
注:如果對專案進行清理操作,這個檔案也會被刪除,而且在重新構建的過程中,只有在所有的 Swift 程式碼都編譯通過的情況下才會重新生成這個檔案。
結合框架的混編
踩坑時間
OC 中的複雜巨集
Swift 編譯器不包含前處理器。取而代之的是,它充分利用了編譯時屬性,生成配置,和語言特性來完成相同的功能。所以對於上述類似的巨集定義,建議用方法重新封裝一次使用。
OC 中的巨集和 Swift 中的類同名
因為 Swift 不能使用 #define
,而 OC 可以,所以你可能會在 OC 中定義一個和 Swift 中類同名字的巨集,如果你從來沒有在 OC 中使用 Swift 程式碼提供的功能(也就是從來沒有 #import XXX-Swift.h
),編譯時不會有任何問題,但是如果一旦使用了,就會報如下的錯誤:
這裡是因為我使用 #define MView (@"MView")
將 MView
定義成了 NSString
型別的巨集,而在 Swift 中 MView
是 UIView
的子類,在 #import "MDemo-Swift.h"
之後, MView
就被重複定義了,從而導致錯誤。
更新:如果 OC 中的類和 Swift 中的類同名,也符合上述情況。
Swift 使用 OC 中的列舉
- 如果只是單純使用值,可以直接使用列舉
- 如果需要對列舉值進行運算,則需要使用
.value
OC 使用 Swift 中的列舉
- 需要在 Swift 的列舉定義前加
@objc
修飾符 - 列舉的型別必須是
Int
@objc / dynamic / NS*
- 如果 Swift 類需要在 OC 中使用,建議繼承 NS* 系列的類
- 如果 Swift 類中的成員或者方法需要在 OC 中使用,使用
@objc
修飾符 - 如果 Swift 類中的成員需要支援 KVC/KVO,使用
dynamic
修飾符
IBOutlet vs IBOutletCollection
Swift 中沒有 IBOutletCollection
,而是如下的方式實現 IBOutletCollection
:
@IBOutlet var labels: [UILabel]!
這個 IBOutlet
在 xib/Storyboard 中的情況如下:
需要注意的是:和 OC 的 IBOutletCollection
不同,labels
中的元素不一定是按照 black,white,blue,green 的順序排列!
重複包含
OC 中可能會碰到 A 類標頭檔案需要包含 B 類標頭檔案,B 類標頭檔案同時也需要包含 A 類標頭檔案的情況,這個時候用 @class
即可解決問題。但是當 OC 中的 A 類標頭檔案需要包含 XXX-Swift.h
,而 XXX-Bridging-Header.h
中又 import
A類標頭檔案,這個時候就比較尷尬了。看下面的例子:
上面例子的情況是這樣的:
首先,我們有兩個 Swift 類 MManager
和 MData
。 一個 OC 類 ViewController
。
然後,MManager
類中的一個方法需要傳入 ViewController
類例項的控制代碼,然後把一個 MData
類例項賦值給 ViewController
類例項中的成員變數 mData
(這個 mData
的型別是 MData
)。
在這種場景下,就會出現重複包含,因為已經有了 MManager
類中的一個方法需要傳入 ViewController
類例項的控制代碼 這樣一個條件,所以我們必須在 XXX-Bridging-Header.h
中 #import "ViewController.h"
,這樣我們就只能通過在 ViewController.m 檔案中去 #import "XXX-Swift.h"
的方式來避免重複包含。此時我們需要將成員變數 mData
定義為 id
型別,當在ViewController.m 檔案中具體用的時候再將其強制轉換為 MData
型別。如下:
即可解決重複包含的問題。
這樣的處理方式存在的問題是成員變數 mData
可以接受任何型別,所以我們在使用的時候要判斷一下 mData
是否是我們需要的資料型別,這裡我們介紹一下怎麼判斷 Swift 的 AnyObject
和 OC 的 id
是什麼型別:
OC:
if ([self.mData isKindOfClass:[MData class]]) { // 如果 self.mData 不是 MData 型別,判斷不成功
}
Swift:
if let data = self.mData as? MData { // 如果 self.mData 不是 MData 型別,判斷不成功
}
properties
如果 OC 的類匯入到 Swift 中使用,類的 properties 會有如下變化:
- Swift 中的屬性都是
noatomic
的,所以 OC 類中的atomic
將會失效 - OC 類中重寫的
getter/setter
方法都將失效
Hello! Swift 2
var -> let
相比 Swift 1.2,Swift 2 強烈要求將在本方法體中值不會改變的量宣告為常量,否則會出現 warning
,所以在編寫 Swift 1.2 的程式碼時,可以提前注意這一點,從而減小轉換成本。
println
Swift 2 中這個方法被刪除,不要使用。
do/while -> repeat/while
因為有變化,所以建議用 for / for…in
代替
面向協議程式設計
從 Swift 2 開始,我們可以對協議進行擴充套件,從此正式開啟了 Swift 的面向協議程式設計時代。因為目前還沒有在專案中具體使用,所以研究不是很深,大家可以參考下面三篇譯文: