文章可能寫得有點晚了,Swift語言已經誕生很久的時間了,現在它已經擠掉了OC很大的市場了,但是,總是存在很多老項目,或者是第三方庫還沒有完全翻譯成Swift,因此,混編還是需要的。雖然現在詳解可能有點晚,不過還是希望能寫一篇關於混編的詳細講解,方便那些遇到困惑的童鞋學習和查閱。
無論是在OC工程裏插入Swift,還是在Swift工程裏插入OC,其實都沒什麼區別,因爲Swift編譯器本來就是用OC寫的,所謂的Swift工程,其背後其實有大量OC代碼參與了編譯,因此,效果是等價的,本文以Swift項目插入OC代碼爲例進行講解。順便強調一下,如果是嵌入純C代碼(.c後綴)的,它和嵌入OC代碼(.m後綴)完全相同,只是不能夠使用OC語法和函數庫而已,因此本文的內容完全適用。至於與C++混編的問題,必須要OC++文件作爲橋樑(.mm後綴),所以,Swift與C++混編問題可以拆解爲Swift與OC混編,以及OC與C++混編問題,關於OC與C++混編問題,請移步至本人早些日子的播客文章,有一篇詳細介紹C、C++和OC混編的問題,因此,在本文不再贅述,本文只針對於Swift與OC混編。
重要!!:本文很多地方用到了“轉化”“轉換”“變成”“等價於”這樣的詞語,只是說,在接口調用時,可以視爲在這個語言中是這樣寫的,但是,並不是說兩種寫法等價,或者說真的有做轉換,代碼本身是沒有進行轉換的,它們都是獨自編譯成機器碼,最後整體鏈接的,並不存在兩種語言的轉化過程,這個請讀者一定要明白,不要被誤導!具體來說,文中說OC中的函數[OC內容]會被轉化爲Swift中的[Swift內容],其實指的是,如果想在Swift中調用OC的函數的話,我們可以理解爲,調用了Swift中的[Swift內容]這中形式的函數,但其實在代碼中並不會出現[Swift內容]這樣的代碼。
重要的事情要再強調一遍!我說的語言1中的A轉換成語言2中的B,意思是在語言2中要想調用語言1中的A,可以當做是調用了語言2中的B。而事實上並沒有B這個東西的存在!切記切記!
例如,我們的工程中主流程存在於main.swift中,然後,我們創建了test.m和test.h,第一次在Swift工程中嵌入.c或者.m文件時Xcode可能會自動生成一個test-Bridging-Header.h的文件,如果沒有自動生成,我們需要手動生成,注意,將test替換爲任意的名字,後面的部分是不能夠改變的,並且,並不需要爲每個OC文件都配一個Bridge頭文件,工程中只需出現一個即可(當然了,你想根據代碼的內容創建多個也是完全可以的)。這個Bridge頭文件的作用是什麼呢?OC代碼中,全局變量、全局函數都是需要聲明才能使用的,因此我們通常使用頭文件來保存函數聲明、變量聲明,當然還有一些宏定義的處理。需要注意的是,宏定義等這些預編譯的東西是不能夠在Swift中使用的。在創建了頭文件以後,還需要確認已經把頭文件加入到了工程編譯路徑中(如下圖)
然後,只要聲明在Bridge頭文件中的(當然也包含Bridge頭文件包含的其他普通頭文件中的聲明),就可以在Swift代碼中使用,同理,如果在Swift代碼中有實現,那麼在Bridge頭文件中加以適當的聲明,那麼,就可以在OC文件中使用(使用時OC文件需要包含Bridge頭文件)。舉一個簡單的例子,在test.m中我們寫了一個函數:
int test1(char a) {
return a + 5;
}
我們要想在Swift中使用這個函數,那麼,我們就需要在Bridge頭文件中寫上:
int test1(char a);
那麼,在main.swift中,可以直接這樣調用:
let a = test1(a: 5)
並且,執行後a的值是10。
同理,如果我們在Swift代碼中有這樣一個函數:
func test2(a: Int) -> Double {
return Double(a) + 0.5
}
如果想在OC代碼中使用,我們就在Bridge頭文件中這樣聲明:
double test2(int a);
然後,我們就可以在OC中這樣使用:
double b = test2(5);
b的值就是5.5,當然,使用之前需要包含頭文件:
#import "test-Bridging-Header.h"
這個很好理解,但是,比較討厭的就是,多數情況下,我們並不是使用這樣簡單的基本變量類型來傳參,我們使用的是比較複雜的類型,因此,我整理了一個表格,表示Swift類型和OC類型的相互對應:
(1)基本變量類型
名稱 | Swift類型 | OC類型 |
---|---|---|
字符型 | Int8 | char |
無符號字符型 | UInt8 | unsigned char |
短整型 | Int16 | short |
無符號短整型 | UInt16 | unsigned short |
長整型 | Int32 | long |
無符號長整型 | UInt32 | unsigned long |
超長整型 | Int64 | long long |
無符號超長整型 | UInt64 | unsigned long long |
浮點型 | Float | float |
雙精度浮點型 | Double | double |
需要注意的是,Swift中的Int,UInt類型以及OC中的int, unsigned類型究竟會轉化爲16位的還是32位的,還是64位的整型,需要根據開發環境的情況,由編譯器決定。因此,如果需要控制數據位數防止溢出的話,那麼並不建議使用這些模棱兩可的數據類型,這得根據實際開發的需求決定。
(2)結構體類型
OC中的結構體類型會被轉化爲Swift中的結構體類型,例如OC中的:
struct st_test {
long a;
char b, c;
};
對應了Swift中的:
struct str_test {
var a: Int32
var b: Int8, c: Int8
}
(3)共合體類型
Swift中不存在共合體類型,因此,OC中的union類型會轉化爲Swift中的struct,但是注意的是,雖然Swift中把他認爲是結構體,但是實際上它還是共合體,因此所有成員是公用內存空間的。Swift中的struct只能在OC中聲明爲struct而不能聲明爲union。
(4)枚舉類型
注意,Swift的enum類型不能轉化爲OC中的enum,只能將其轉化爲類,才能轉化爲OC的類,而OC中的enum會被轉化爲Swift中的全局變量以及類型重命名,例如OC中的:
enum en_test {
a, b,
c = 5, d
};
會被轉化爲Swift中的:
typealias en_test = Int32
let a: Int32 = 0
let b: Int32 = 1
let c: Int32 = 5
let d: Int32 = 6
這個是非常不一樣的轉化,一定一定一定要注意!
(5)數組類型
Swift中的數組(Array)會被轉化爲OC中的NSMutableArray,而OC中的數組會被轉化爲Swift中的元組,例如OC中的:
float arr[5];
會被轉化爲Swift中的:
var arr: (Float, Float, Float, Float, Float)
多維數組則會轉化爲嵌套元組,例如OC中的:
long arr_3[2][3][4];
會被轉化爲Swift中的:
var arr_3: (((Int32, Int32, Int32, Int32), (Int32, Int32, Int32, Int32), (Int32, Int32, Int32, Int32)), ((Int32, Int32, Int32, Int32), (Int32, Int32, Int32, Int32), (Int32, Int32, Int32, Int32)))
嵌套順序是OC中越靠後的數字在Swift中越靠內層。
關於Swift數組的轉換見後面的“類與對象”部分。
(6)非對象指針
OC中的指針,會被轉化爲Swift中的UnsafePointer類型,並且,會根據關鍵字修飾的不同有不同的轉化,見下表:
名稱 | Swift類型 | OC類型 | 示例(Swift) | 示例(OC) |
---|---|---|---|---|
普通指針 |
UnsafeMutablePointer<T> |
T * |
UnsafeMutablePointer<Int8> |
char * |
不可變指針 | UnsafePointer<T> | T *__nonnull | UnsafePointer<Float> | float *__nonnull |
(以下可變與不可變都是按照上面的方式,不再單獨列出) | ||||
數組指針 | UnsafeMutablePointer<((T, T, ...))> | T (*)[n] |
UnsafeMutablePointer<((Int16, Int16, Int16))> |
short (*)[3] |
函數指針 | (T1, T2, ...) -> T | T (*)(T1, T2, ...) | (a: Int32, b: Float) -> Double | double (*)(long a, float b) |
特別特別要注意的是,雖然數組會被轉化爲元組,但是,數組指針在轉化後會加上一層元組,也就是說n維數組指針在轉化後會被變成UnsafePointer的n+1維的元組的實例。
還有一點需要關注的是,如果數組在函數參數中,是會把最外層退化爲指針的,那麼轉化成Swift的時候也要按數組指針來對待,例如OC中:
void func1(char a[2][3]);
會被轉化爲Swift中的:
func func1(a: UnsafeMutablePointer<((Int8, Int8, Int8))>)
因爲這裏的char a[2][3]其實是被轉化爲了char (*a)[3]。
(7)普通類/對象
這裏的普通對象指的是非類庫中的對象,以及一部分類庫中的對象(例外將在後面“特殊類/對象”的部分中)。
普通的類將會被直接轉化,例如OC中的如下代碼:
// 類型示例
@interface Test_Cl : Test_PC {
long a;
}
- (char)func1;
- (void)func2:(char)arg1 andOA:(unsigned short)arg2;
+ (long)func3;
@end
// 省略實現
// 調用示例
Test_C1 *t1 = [[Test_C1 alloc] init];
char o1 = [t1 func1];
[t1 func2:'a' andOA:5];
[Test_C1 func3];
會被轉化爲Swift中的:
class Test_C1: Test_PC {
private var a: Int32
func func1() -> Int8 {// 省略實現}
func func2(_ arg1: Int8, andOA arg2: UInt16) {// 省略實現}
static func func3() {// 省略實現}
}
// 調用示例
var t1 = Test_C1()
var o1: Int8 = t1.fun1
t1.func2(Int8('a'), andOA 5)
Test_C1.func3()
內容比較多,希望讀者仔細觀察,OC中的方法名中後續部分會被轉化爲Swift方法中的外部變量名,並且,由於OC中的方法名會包含第一個參數的提示,因此轉化爲Swift後,第一個參數一定是無外部參數名的,提示部分會顯示到方法名中(請仔細觀察上面func2的示例)。OC中直接寫在類頭大括號裏的變量全部都會被轉化爲Swift中的private,注意@public、@protected和@private關鍵字會被無視,統一都會轉化爲Swift中的private,而Swift中的private變量會被轉化爲OC中的@private修飾的變量。
很多關鍵字與類組合時,在轉化時都會有變化,本節只是簡單介紹一下,詳細的情況請看後面部分。
(8)const
大體來說,OC中沒有用const修飾的,會轉化爲Swift的var修飾,用const修飾的會被轉化爲Swift中的let,但是如果修飾在嵌套類型的非最外層則會被無視,例如:
Swift中 | OC中 |
---|---|
let a: Int | const int a; |
var a: UnsafeMutablePointer<Int8> | char *a; |
var a: UnsafeMutablePointer<Int8> | const char *a; |
let a: UnsafeMutablePointer<Int8> | char *const a; |
var a: UnsafeMutablePointer<UnsafeMutablePointer<Int8>> | char *const *a; |
但是在搭配很多類庫對象的時候,是會有不同的,詳見“特殊類/對象”部分。
(9)Block與閉包
OC中的Block和函數指針一樣,都會被轉化爲Swift中的閉包,但是,Swift的閉包只會被轉化爲OC中的Block而不是函數指針,例如OC中的:
int (^const f1)(char, char) = ^(char a, char b) {
return a + b;
}
會被轉化爲Swift中的:
let f1: (Int8, Int8) -> Int = {(a, b) in
return a + b
}
需要注意的是,OC的Block和函數指針會被轉化爲Swift的非逃逸閉包,相當於用@unscaping修飾。
(10)特殊類/對象
特殊的主要發生在基本類庫中,這個和Swift版本有關係,第一版的Swift是沒有這些所謂特殊的類的,而後面因爲類庫命名有很大更改,並且,Swift語法也在不斷改變,爲了讓開發者使用方法,所以纔出現了一些特殊的轉換(注:寫本文針對於Swift 4版本,不一定適用於其他版本,因此請讀者關注自己使用的Swift版本,如果你在閱讀本文時已經有了更新的版本,那麼可能會有一些差別,但是這並不影響你閱讀本節,因爲這會給你一個思路,而具體細節可以查閱蘋果的官方文檔,有關於每個版本更新的語法改變說明)。
這部分比較複雜,所以,這裏還是用表格的方式呈現:
說明 | Swift | OC | 示例(Swift) | 示例(OC) |
---|---|---|---|---|
同一類型元組 | (T, T, T...) | T[] | let a = (1, 2, 3, 4) | const int a[4] = {1, 2, 3, 4}; |
數組 | [T] | NSArray | let a = [1, 2, 3] | NSArray *a = @[1, 2, 3]; |
可變數組 | [T] | NSMutableArray | var a [1, 2, 3] | NSMutableArray *a = @[1, 2, 3]; |
字符串 | String | NSString | let a = "123" | NSString *a = @"123"; |
可變字符串 | String | NSMutableString | var a = "123" | NSMutableString *a = @"123"; |
字典 | [K, V] | NSDictionary | let a = [1: "one", 2: "" two] | NSDictionary *s = [NSDictionary dictionaryWithObjectsAndKeys: @1, @"one", @2, @"two"]; |
可變字典 | [K, V] | NSMutableDictionary | 略 | 略 |
任意 | Any | void * | let a: Any | void *const a; |
任意對象 | AnyObject | void *__nonnull | var a: AnyObject | void *__nonnull a; |
特別需要注意的是,C語言字符串(char數組)會被轉化爲UnsafePointer<Int8>,而不是String。
另外,一些NS開頭的類在Swift中也是可用的,他們會被直接轉換,例如Swift中的NSString仍然會被轉化爲OC中的NSString,但是,OC中這些NS開頭的則會轉化爲Swift中的基本類型,例如NSMutableString就會被轉化爲Swift中以var修飾的String。
對於嵌套類型,則全部會轉化爲對象,例如Swift的[Int:[Int]]類型就會被轉化爲NSArray類型,而其中的成員都已經被轉化爲了NSObject或其子類,Int會被轉化爲NSNumber,而[Int]則會被轉化爲NSArray,操作NSArray時利用多態性,其成員都是NSObject *類型。
(11) 指針與inout參數
這個說起來應該不算混編內容,應當是純Swift內容,但是由於單純使用Swift時真是太少使用指針了,反而是在混編時經常會遇到,因此在這裏講解。
在Swift中,如果函數參數是UnsafePointer類型(或其他的指針類型)時,可以直接傳進inout參數,例如:
// 函數定義
func func1(a: UnsafeMutablePointer<Int>) {// 省略實現}
// 函數調用
var a = 0
func1(&a) // 合法
也就是說,參數是指針類型時,可以把它與用inout修飾的函數參數等同對待。因此,在混編時,如果一個OC函數參數需要傳指針的話,在Swift裏調用時可以直接傳&變量,而不用去用複雜的語句包裝成指針類型再傳參。
(12)可變類型的函數參數
Swift函數的形參是不能夠改變值的,曾經的Swift可以在參數前用var修飾使其可變,但是在Swift3中把這個語法取消了,因此,Swift函數轉化成OC時,所有的參數都會默認帶上const。例如Swift中:
func f1(a: Int, b: Int) {// 省略實現}
會被轉化爲OC中的:
void f1(const int a, const int b) {// 省略實現}
而當OC轉Swift的時候,這裏的const是會被丟棄的(因爲在Swift中本來就不可變)。
(13)可選類型
通常情況下,OC轉Swift的時候不會轉爲Optional類型,但是,在之前提到的那些特殊類的轉化中,是存在可選類型的,因爲這部分的let和var其實對應了不同的類,所以用於處理__nonnull關鍵字的,就需要可選類型來擔當了,例如:
Swift | OC | 示例(Swift) | 示例(OC) |
---|---|---|---|
String? | NSString * | let a: String? = "123" | NSString *const a = @"123"; |
String | NSString *__nonnull | let a = "123" | NSString *__nonnull a = @"123" |
String | NSMutableString *__nonnull | var a = "123" | NSMutableString *__nonnull a = @"123" |
其他的那些特殊類都是一樣的道理,let和var控制的是是否有Mutable,而是否可選控制的是有無__nonnull。
(14)成員變量
Swift中沒有被private修飾的成員變量,會轉化爲OC中的@property,反之亦然。例如Swift中的:
class Test_Cl {
var a: Int
}
被轉化爲OC中的:
@interface Test_Cl
@property int a;
@end
也就是說,在OC中是會自動生成set和get方法的,但是如果手動寫OC的get和set,是不會轉化爲Swift的成員的,仍然會轉化爲一個private成員加兩個普通的方法。
(15)構造函數
類庫中大部分類都對Swift重構了,所以構造函數這裏可以放心調用,但是,如果沒有進行重構的話,只有標準OC構造函數和Swift無參構造函數可以互相轉化,而OC的非標準構造函數將會轉化爲Swift的類函數,Swift的有參構造函數則無法被轉化。OC中的:
@interface Test_Cl : Test_PC
- (instancetype)init; // 只有這個函數頭是標準的構造函數
- (instancetype)initWithName:(NSString *)name; // 這個就是普通函數了
@end
會被轉化爲Swift中的:
class Test_Cl: Test_PC {
init() {// 省略實現}
static func initWithName(_ name: String) {//這就是個普通函數了,省略實現}
}
(15)不能進行轉化的
除了上文提到的一些會丟棄的部分,還有一些語法由於沒有提供語法接口,是不能夠跨語言調用的,例如:運算符重載,計算方法,逃逸閉包,含有外部引用的閉包,重載函數, 有參構造函數等等。所以在混編時要注意避免這些問題,否則這部分代碼將不能跨語言調用。
讀者應該能夠發現,大多數場景下,都是在Swift工程中調用OC的接口,很少會有反過來的,基本上OC的都系在Swift中都能夠得到比較好的兼容,而反過來的話則會有很多不能夠調用的情況。
以上就是本人對Swift於OC混編的總結,希望能夠幫到在此糾結的朋友,幫大家度過轉化期。但是對於需要繼續長久使用的OC工程或是第三方庫,還是應當儘早完成對Swift語言的重構,這樣就可以避免在混編過程中出現的問題,同時還可以充分利用Swift語言的特性優勢,編寫出更優秀的代碼。
如果讀者還有什麼問題,歡迎留言討論,本文的一切權利歸本人所有,如果讀者希望轉發,請在開頭標註出轉發字樣以及出處,多謝配合!