1. 程式人生 > >Objective-C 與 Swift 混編之路

Objective-C 與 Swift 混編之路

本文內容基於 Xcode 6.4 和 Swift 1.2

重要資料

為什麼要混編?

  • 語言發展趨勢(TIOBE),Swift 排行持續上升, OC 排行呈重力下降
  • 專案正常迭代需要
    • 很多第三方庫仍然使用 OC 實現
    • 專案中原來已經用 OC 實現的模組如果使用 Swift 重寫,代價稍大
    • 我們需要在專案中使用 Swift 才能真正碰到問題,解決問題

注:不是為了混編而混編。混編只是在對開發資源、專案管理和技術發展趨勢進行綜合衡量之後做出的比較合理的選擇。

如何開始混編?

步驟

  1. 建立工程,Language 選擇 Swift 或 Objective-C 都可以。

    這裡寫圖片描述

  2. 建立 Swift 檔案並新增 bridging header 檔案

    這裡寫圖片描述

    新增 Swift 檔案時 Xcode 會自動提示你新增 bridging header 檔案,選擇 Yes 即可

  3. 進行兩處關鍵設定

    這裡寫圖片描述
    這裡寫圖片描述

    這兩處設定 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 中 MViewUIView 的子類,在 #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 類 MManagerMData。 一個 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 會有如下變化:

  1. Swift 中的屬性都是 noatomic 的,所以 OC 類中的 atomic 將會失效
  2. 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 的面向協議程式設計時代。因為目前還沒有在專案中具體使用,所以研究不是很深,大家可以參考下面三篇譯文: