Objective-C 與 Swift 混編在使用上主要依賴兩個頭文件:ProjectName-Bridging-Header.h 和 ProjectName-Swift.h。html
對於 Swift 調用 Objective-C,在 ProjectName-Bridging-Header.h 中 import 要使用的 Objective-C 頭文件。git
對於 Objective-C 調用 Swift,須要編譯過程當中生成的 ProjectName-Swift.h 文件,此文件會將 Objective-C 須要使用的 Swift 類轉成 Objective-C 格式的 .h 文件。下面就是一段例子:github
當 Objective-C 與 Swift 進行混編時,編譯的過程(Pipeline)是:objective-c
在開發過程當中,有時候會遇到 ’ProjectName-Swift.h’ file not found
錯誤,而 ProjectName-Swift.h 又是在編譯時動態生成的,當出現這樣的錯誤時就比較迷惑。下面就分析一下出現該錯誤的緣由。算法
@objc (TTLesson)
class lesson: NSObject {
}
複製代碼
假設有一個 Swift 類 Lesson,Objective-C 的 TTLessonViewController 中使用了該類,所以須要引入 MixedProj-Swift.h 頭文件。編程
// TTLessonViewController.h
#import "MixedProj-Swift.h"
@interface TTLessonViewController: UIViewController
@property (nonatomic, strong) TTLesson *lesson;
@end
複製代碼
若是 TTLessonViewController 又要在 Swift 中使用,須要將 TTLessonViewController.h 加入到 MixedProj-Bridging-Header.h 中swift
// MixedProj-Bridging-Header.h
#import "TTLessonViewController.h"
複製代碼
此時編譯就會出現 MixedProj-Swift.h file not found
的錯誤。從新回顧一下混編時的 Pipeline:xcode
解決辦法就是不顯式的 import 頭文件,而是使用前置聲明(Forward Declaration),打破 Circle Reference。bash
// TTLessonViewController.h
@class TTLesson;
@interface TTLessonViewController: UIViewController
@property (nonatomic, strong) TTLesson *lesson;
@end
複製代碼
筆者在瞭解 Objective-C 與 Swift 混編時一直在思考語音爲何能混編,語言究竟是如何混編的?筆者瞭解的也很少,這裏只是談談本身的一些思考,拋磚引玉。服務器
對於語言混編,不妨嘗試着迴歸到程序本質的角度進行看待:
程序 = 數據結構 + 算法 / 數據 + 邏輯
在機器碼層面,全部編程語言都轉化成了機器指令:
那語言想要混編,就須要在數據和邏輯之間創建鏈接:
最後發散一下,既然是創建鏈接,那麼經過 TCP/HTTP/IPC 等方式創建的通訊能夠當作更廣義上的混編。例如客戶端以 REST API 的方式向服務器端請求數據也能夠當作客戶端語言 Objective-C/Swift/Kotlin/Java 與服務器語言 Java/Go/Python 的混編,只是兩端之間使用 URL(邏輯)+ JSON(數據)這種通用格式/協議的方式創建起了鏈接。
Module 是 WWDC 2013 就引進的技術,在 Xcode 的 Build Setting 中能看到 Enable Modules (C and Objective-C) 的設置項,在 OTHER_CFLAGS 中也看到了 -fmodule-map-file=「xxx.modulemap」,可是一直對其不太瞭解,一項技術的出現老是爲了解決某些痛點,那 Module 是爲了處理哪些問題呢?
之前在 C/C++/Objective-C 中,源文件中引入的頭文件在編譯時須要進行展開,預處理宏等相關操做。這樣的方式存在如下幾種問題:
因爲頭文件是一塊兒展開後再統一處理宏,當宏重名時,頭文件引入的順序就會致使不同的結果,而且宏只是簡單粗暴的文本替換,也存在宏污染的可能。例如,AppDelegate.m 中定義了一個 readonly 的宏,與 Objective-C 中屬性的關鍵字衝突了。
// iAd/ADBannerView.h
@interface ADBannerView : UIView
@property (nonatomic, readonly) ADAdtype type;
@end
// AppDelegate.m
#define readonly 0x01
#import <iAd/iAd.h>
@implementation AppDelegate
// ....
@end
複製代碼
因爲是文本替換,預處理完以後,ADBannerView 的 type 屬性中,readonly 被替換成了 0x01,致使編譯錯誤,而且很難定位到問題根源。
// AppDelegate.m
#define readonly 0x01
@interface ADBannerView : UIView
@property (nonatomic, 0x01) ADAdtype type;
@end
@implementation AppDelegate
// ....
@end
複製代碼
因爲每一個源文件中的頭文件都須要展開、預處理,假設有 M 個源文件,N 個頭文件,須要 M * N 的編譯時間。
爲了優化這個問題,Objective-C 引入了預編譯頭文件(Pre-Compiled Header),將公用的頭文件放入預編譯頭文件中預先進行編譯,而後在真正編譯工程時再將預先編譯好的產物加入到全部待編譯的 Source 中去,來加快編譯速度。
可是 PCH 文件會致使裏面的頭文件是全局可見的,至關於變成了全局依賴,可能會致使 Namespace Pollution。而且當 PCH 文件變成龐大時,仍是會致使預編譯頭文件的時間變長。
在使用框架時,須要 import 正確的頭文件,否則可能致使編譯問題。另外,僅僅 import 頭文件是不夠的,須要在設置中連接對應的庫,不是很方便。
framework module UIKit {
umbrella header "UIKit.h"
module * {export *}
link framework "UIKit"
}
複製代碼
Modules 至關於將框架進行了封裝,看一下上面 UIKit 的 modulemap 文件,Module 在 modulemap 中定義了框架的三大核心要素:
在實際編譯時,加入到一個用來存放已編譯添加過的 Modules 列表中。若是在編譯的文件引用到某個 Modules 的話,將首先在這個列表內查找,找到的話說明已經被加載過則直接使用已有的,若是沒有找到,則把引用的頭文件編譯後加入到這個表中。
所謂 umbrella header,就是 includes all of the headers in its directory,一個包含框架內全部須要開放頭文件的 Wrapper 頭文件。爲何叫」雨傘「呢?筆者猜想是「雨傘」能比較形象的描述這種頭文件的做用。能夠想象一下雨傘☂️的形狀和做用,最頂部的那個尖就像把全部東西集中起來,雨傘也將傘下的細節給遮蓋起來了。
@import ModuleName;
@import ModuleName.SubmoduleName;
複製代碼
經過 @import
使用 Module,而且 Module 支持 Submodule。若是在 Build Settings 中將 Enable Modules (C and Objective-C) 打開,不須要改變代碼,即便使用的是舊的 #import
方式,編譯器也會在編譯的時候自動地把可能的地方換成 Modules 的寫法去編譯的。
總之,Module 有以下特色:
下一篇,將講一下如何將一個 Objective-C 與 Swift 混編的代碼,從依賴混亂不完備的 Git Submodule,一步步的抽成 Development Pod,最後變成二進制的過程與遇到的坑。
Article by Joe Shang
參考: