作者:Umberto Raimondi,原文鏈接,原文日期:2016-04-07
譯者:shanks;校對(duì):pmst;定稿:CMB
從 Swift 開源到現(xiàn)在,只有短短的幾個(gè)月時(shí)間,Swift 卻已經(jīng)被移植到了許多新的平臺(tái)上,還有一些新的項(xiàng)目已經(jīng)使用了 Swift。這類移植,每個(gè)月都在發(fā)生著。
在不同平臺(tái)下混合使用 Swift 和 C 的可行性,看起來是一件非常難的實(shí)踐,只有非常有限的實(shí)踐資源,當(dāng)然這是和你去封裝一個(gè)原生庫(kù)對(duì)比起來看的,你可以在你代碼運(yùn)行的平臺(tái)上輕松地封裝一個(gè)原生庫(kù)。
官方文檔 Using Swift with Cocoa and Objective-C 已經(jīng)系統(tǒng)地講解了有關(guān)與 C 語(yǔ)言互調(diào)的基本知識(shí)。但僅限于此,尤其是在實(shí)際的場(chǎng)景中如何去使用這些橋接函數(shù),感覺仍然是一臉懵逼的。僅有少數(shù)博客文章會(huì)有此文檔筆記和使用講解。
這篇文章將在一些不是那么明顯的細(xì)節(jié)地方給你一些啟發(fā),同時(shí)給出一些實(shí)際的例子,講解如何與 C 語(yǔ)言的 API 互調(diào)。這篇文章主要是面向那些計(jì)劃在 Linux 下進(jìn)行 Swift 開發(fā)的同學(xué),另外文中的一些解釋,同樣適用于基于 Darwin 的操作系統(tǒng)。
首先簡(jiǎn)要介紹如何把 C 類型導(dǎo)入 Swift 中,隨后我們將深入研究有關(guān)指針,字符串和函數(shù)的使用細(xì)節(jié),通過一個(gè)簡(jiǎn)單的教程學(xué)習(xí)使用 LLVM 模塊創(chuàng)建 Swift 和 C 混編的項(xiàng)目。
內(nèi)容介紹
<a name="c_type"></a>
C 類型
每一個(gè) C 語(yǔ)言基本類型, Swift 都提供了與之對(duì)應(yīng)的類型。在 Swift 中調(diào)用 C 方法的時(shí)候,會(huì)用到這些類型:
| C 類型 | Swift 對(duì)應(yīng)類型 | 別名 |
|---|---|---|
| bool | CBool | Bool |
| char,unsigned char | CChar, CUnsignedChar | Int8, UInt8 |
| short, unsigned short | CShort, CUnsignedShort | Int16, UInt16 |
| int, unsigned int | CInt, CUnsignedInt | Int32, UInt32 |
| long, unsigned long | CLong, CUnsignedLong | Int, UInt |
| long long, unsigned long long | CLongLong, CUnsignedLongLong | Int64, UInt64 |
| wchar_t, char16_t, char32_t | CWideChar, CChar16, CChar32 | UnicodeScalar, UInt16, UnicodeScalar |
| float, double | CFloat, CDouble | Float, Double |
官方文檔中對(duì)上面表格也有介紹,展示了 Swift 類型和對(duì)應(yīng)的 C 別名。
即使在你寫一些需要調(diào)用 C APIs 的代碼時(shí),你都應(yīng)該盡可能地使用 Swift 的 C 類型。你會(huì)注意到,大多數(shù)從 C 轉(zhuǎn)換到 Swift 的類型,都是簡(jiǎn)單地使用了常用的 Swift 固定大小的類型,而這些類型,你應(yīng)該已經(jīng)相當(dāng)熟悉了。
<a name="arrays_and_structs"></a>
數(shù)組和結(jié)構(gòu)體
讓我們接下來聊聊復(fù)合數(shù)據(jù)結(jié)構(gòu):數(shù)組和結(jié)構(gòu)體。
理想的情況下,你希望定義一個(gè)如下全局?jǐn)?shù)組:
c
//header.h
char name[] = "IAmAString";
在 Swift 中,有可能會(huì)被轉(zhuǎn)換成一個(gè) Swift 字符串,或者至少是某種字符類型的數(shù)組。當(dāng)然,當(dāng)我們真正在 Swift 中使用這個(gè)導(dǎo)入的 name 數(shù)組,將會(huì)出現(xiàn)以下結(jié)果:
print(name) // (97, 115, 100, 100, 97, 115, 100, 0)
這個(gè)事實(shí)告訴我們,當(dāng)你在做一個(gè) Swift/C 混合的應(yīng)用下時(shí),在 C 語(yǔ)言層面,推薦使用指針表示一個(gè)對(duì)象的序列,而不是使用一個(gè)普通的數(shù)組。這樣能避免在 Swift 語(yǔ)言層面下痛苦的轉(zhuǎn)換。
但是等一下,如果我們使用一段復(fù)雜的代碼轉(zhuǎn)換數(shù)字元組,恢復(fù)成之前定義為數(shù)組的全局字符串,是否更加好呢?答案是否定的,我們將會(huì)在討論指針的時(shí)候,介紹如何使用一小段代碼如何復(fù)原數(shù)組元組。
幸運(yùn)的是,以上的情況不會(huì)在處理結(jié)構(gòu)體時(shí)候發(fā)生,將會(huì)如預(yù)期的轉(zhuǎn)換為 Swift 的結(jié)構(gòu)體,結(jié)構(gòu)體的成員也將會(huì)按照預(yù)期的方式轉(zhuǎn)換,每一個(gè)成員都會(huì)轉(zhuǎn)換成對(duì)應(yīng)的 Swift 類型。
比如,有以下的結(jié)構(gòu)體:
c
typedef struct {
char name[5];
int value;
int anotherValue;
} MyStruct;
這個(gè)結(jié)構(gòu)體將會(huì)轉(zhuǎn)換成一個(gè) MyStruct 的 Swift 結(jié)構(gòu)體。結(jié)構(gòu)體的構(gòu)造函數(shù)的轉(zhuǎn)換也很簡(jiǎn)單,跟我們想象中的一樣:
let ms = MyStruct(name: (0, 0, 0, 0, 0), value: 1, anotherValue:2)
print(ms)
下文某個(gè)章節(jié),我們將看到這并非是唯一方法去構(gòu)造和初始化一個(gè)結(jié)構(gòu)體實(shí)例,尤其是在我們只需要一個(gè)指向空對(duì)象的指針時(shí),更簡(jiǎn)單的方式應(yīng)該是手動(dòng)分配一個(gè)新的空結(jié)構(gòu)體指針實(shí)例。
<a name="enums"></a>
枚舉
如果你需要使用 Swift 訪問 C 的枚舉,首先在 C 中定義一個(gè)常見的枚舉類型:
c
typedef enum ConnectionError{
ConnectionErrorCouldNotConnect = 0,
ConnectionErrorDisconnected = 1,
ConnectionErrorResetByPeer = 2
}
當(dāng)轉(zhuǎn)換到 Swift 中時(shí)候,會(huì)與你期望的情況完全不同, Swift 中的枚舉是一個(gè)結(jié)構(gòu)體,并且會(huì)有一些全局變量:
struct ConnectionError : RawRapresentable, Equatable{ }
var ConnectionErrorCouldNotConnect: ConnectionError {get}
var ConnectionErrorDisconnected: ConnectionError {get}
var ConnectionErrorResetByPeer: ConnectionError {get}
顯然這樣做的話,我們將喪失 Swift 原生枚舉提供的所有功能點(diǎn)。但是如果在 C 中使用一個(gè)特定的宏定義的話,我們將得到我們想要的結(jié)果:
c
typedef NS_ENUM(NSInteger,ConnectionError) {
ConnectionErrorCouldNotConnect,
ConnectionErrorDisconnected,
ConnectionErrorResetByPeer
}
使用NS_ENUM宏定義的枚舉(關(guān)于這個(gè)宏定義如何對(duì)應(yīng)到一個(gè)經(jīng)典的 C 枚舉的知識(shí),請(qǐng)參看這里),以下代碼展示在 Swift 如何導(dǎo)入這個(gè)枚舉:
enum ConnectionError: Int {
case CouldNotConnect
case Disconnected
case ResetByPeer
}
需要注意的是,枚舉值的轉(zhuǎn)換是去掉了枚舉名的前綴了的,這是 Swift 其中一個(gè)轉(zhuǎn)換的規(guī)則,你也會(huì)在使用標(biāo)準(zhǔn)的基于 Swift iOS/OSX 框架時(shí)候看到這種規(guī)則。
另外, Swift 提供了 NS_OPTIONS 宏定義,用于定義一個(gè)可選項(xiàng)集合,遵從 OptionSetType 協(xié)議(目前為 OpertionType )。關(guān)于此宏定義的更多介紹,請(qǐng)參看官方文檔。
<a name="unions"></a>
聯(lián)合體
接下來讓我們看看聯(lián)合體,一個(gè)有趣的 C 類型,在 Swift 中沒有對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)。
Swift 僅部分支持聯(lián)合體,意思是當(dāng)一個(gè)聯(lián)合體被導(dǎo)入時(shí),不是每一個(gè)字段都會(huì)被支持,造成的結(jié)果就是,你在 C 中定義的某些字段將不可用(截止目前,沒有一個(gè)文檔說明什么不被支持)。
讓我們用一個(gè)實(shí)際的例子來說明這個(gè)被文檔遺忘的 C 類型:
c
//header.h
union TestUnion {
int i;
float f;
unsigned char asChar[4];
} testUnion;
在這里我們定義一個(gè) TestUnion 類型,還有一個(gè)相關(guān)的 testUnion 聯(lián)合體變量,一共有 4 字節(jié)的內(nèi)存,其中每一個(gè)字段代表不同的視角,在 C 語(yǔ)言中,我們可以訪問 testUnion 變量,這個(gè)變量可以是整形,浮點(diǎn)數(shù)和 char 字符串。
由于在 Swift 中,沒有類似的數(shù)據(jù)結(jié)構(gòu)與聯(lián)合體對(duì)應(yīng),所以這種類似將在 Swift 中被視作一個(gè)結(jié)構(gòu)體:
strideof(TestUnion) // 4 bytes
testUnion.i = 33
testUnion.f // 4.624285e-44
testUnion.i // 33
testUnion.asChar // (33, 0, 0, 0)
testUnion.f = 1234567
testUnion.f // 1234567
testUnion.i // 1234613304
testUnion.asChar // (56, 180, 150, 73)
正如我們對(duì)聯(lián)合體期望那樣,上面第一行代碼驗(yàn)證這個(gè)類型的確只占 4 個(gè)字節(jié)的內(nèi)存長(zhǎng)度。接下來的代碼,修改其中一個(gè)字段,然后驗(yàn)證包含在其他字段中得值是否同時(shí)被更新。但是為什么當(dāng)我們?cè)O(shè)置 testUnion 的整型字段為 33 時(shí),我們獲取對(duì)應(yīng)的 float 字段的值卻為 4.624285e-44?
這就跟聯(lián)合體如何工作有關(guān)了。你可以把一個(gè)聯(lián)合體想象為一個(gè)字節(jié)包,根據(jù)每個(gè)字段組成的格式化規(guī)則進(jìn)行讀寫,在上面的例子中,我們?cè)O(shè)置的 4 個(gè)字節(jié)的內(nèi)存區(qū)域,與 Int32(32)的字節(jié)內(nèi)容組成是相同的,然后我們讀取這4個(gè)字節(jié)的內(nèi)存區(qū)域,解釋成為的字節(jié)模式是一個(gè) IEEE 的浮點(diǎn)數(shù)。
我們使用一個(gè)有用的(但是危險(xiǎn)的) unsafeBitCast 函數(shù)來驗(yàn)證上面的解釋:
var fv:Float32 = unsafeBitCast(Int32(33), Float.self) // 4.624285e-44
以上代碼的作用,與使用聯(lián)合體的浮點(diǎn)類型,訪問一個(gè)包含 Int32(33) 的字節(jié)內(nèi)存做得事情一樣。賦值給了一個(gè)浮點(diǎn)類型,并且沒有做任何的轉(zhuǎn)換和內(nèi)存安全檢查。
到目前為止我們已經(jīng)學(xué)習(xí)了聯(lián)合體的行為,那么我們能在 Swift 中手動(dòng)實(shí)現(xiàn)一個(gè)類似的結(jié)構(gòu)體嗎?
即使沒有去查看源代碼,我們也可以猜到 TestUnion 只是一個(gè)簡(jiǎn)單的結(jié)構(gòu)體,只有4個(gè)字節(jié)的內(nèi)存數(shù)據(jù)塊(是那種形式的并不重要),我們只能訪問其中的計(jì)算屬性,這些計(jì)算屬性把所有的轉(zhuǎn)換細(xì)節(jié)封裝在了 set/get 方法中了。
<a name="the_size_of_things"></a>
關(guān)于長(zhǎng)度的那些事
在 Swift 中,你可以使用 sizeof 函數(shù)獲取特定類型(原生的和組合的)的數(shù)據(jù)長(zhǎng)度,就像你在 C 語(yǔ)言中使用 sizeof 操作符一樣。Swift 同時(shí)還提供了一個(gè) sizeOfValue 函數(shù),返回一個(gè)類型給定值的數(shù)據(jù)長(zhǎng)度。
但是 C 語(yǔ)言中 sizeof 返回值包含了附加填充保證內(nèi)存對(duì)齊,而 Swift 中的函數(shù)只是返回變量的數(shù)據(jù)長(zhǎng)度,不管究竟是如何在內(nèi)存中存儲(chǔ)的,然而這在大多數(shù)情況與我們的期望背道相馳。
我想你應(yīng)該可以猜到, Swift 同時(shí)也提供了 2 個(gè)附加的函數(shù),正確地得到變量或者類型的長(zhǎng)度,并且計(jì)算包括用于對(duì)齊需要的額外空間,大多數(shù)情況下,你應(yīng)該習(xí)慣替換之前的一些函數(shù)而使用 strideof 和 strideOfValue 方法,讓我們通過一個(gè)例子來看看 sizeof 和 strideof 返回的區(qū)別:
print(strideof(CChar)) // 1 byte
struct Struct1{
let anInt8:Int64
let anInt:Int16
let b:Bool
}
print(sizeof(Struct1)) // 11 (8+2+1) byte
print(strideof(Struct1)) // 16 (8+4+4) byte
同時(shí)當(dāng)計(jì)算額外的空間時(shí),需要遵守處理器架構(gòu)的對(duì)齊規(guī)則,不同的處理器架構(gòu)下,strideof 和 sizeof 之間返回的值會(huì)有所不同,一個(gè)附加的工具函數(shù)alignof可供使用。
<a name="null_nil_0"></a>
Null, nil 和 0
幸運(yùn)的是, Swift 沒有提供一個(gè)額外的常量來表示 null 值,你只能使用 Swift 的 nil ,不管指定的變量或者參數(shù)的類型是什么。
在后面談到指針時(shí),nil 作為參數(shù)傳遞將會(huì)自動(dòng)被轉(zhuǎn)換成一個(gè) null 指針。
<a name="macros"></a>
宏定義
簡(jiǎn)單的 C 宏定義會(huì)轉(zhuǎn)換成 Swift 中得全局常量,與 C 中的常量有點(diǎn)類似:
c
#define MY_CONSTANT 42
將被轉(zhuǎn)換成:
let MY_CONSTANT = 42
更加復(fù)雜的宏定義和預(yù)處理指令會(huì)徹底被 Swift 忽略摒棄。
Swift 也提供了一個(gè)簡(jiǎn)單的條件式編譯聲明方式,指明某些具體的代碼片段只能在特定的?操作系統(tǒng),架構(gòu)或版本的 Swift 中使用。
#if arch(arm) && os(Linux) && swift(>=2.2)
import Glibc
#elseif !arch(i386)
import Darwin
#else
import Darwin
#endif
puts("Hello!")
在這個(gè)例子中,我們根據(jù)不同的編譯環(huán)境,ARM Linux 或者其他環(huán)境,決定需要導(dǎo)入的標(biāo)準(zhǔn) C 庫(kù),用于在不同的環(huán)境中編譯和使用。
這些用來定制編譯行為的可用函數(shù)是: os() (可用值: OSX, iOS, watchOS, tvOS, Linux), arch() (可用值: x86_64, arm, arm64, i386) 和 swift() (要求參數(shù)值指定大于等于某個(gè)版本號(hào))。這些函數(shù)可以結(jié)合一些基本的邏輯與運(yùn)算符一起使用,構(gòu)建更加復(fù)雜的規(guī)則:&&, ||, !。
盡管你可能對(duì)此不太了解,你只要記住在 OSX 中應(yīng)該導(dǎo)入 Darwin(或者其中某個(gè)依賴它的框架)到你的項(xiàng)目中就可以了,用于獲取 libc 的函數(shù), 而在 Linux 的平臺(tái)上,你應(yīng)該導(dǎo)入 Glibc。
<a name="working_with_pointers"></a>
指針操作
指針被自動(dòng)的轉(zhuǎn)換為不同類型的 UnsafePointer<Memory> 對(duì)象,對(duì)象取決于指針指向值的特征:
| C 指針 | Swift 類型 |
|---|---|
| int * | UnsafeMutablePointer<Int32> |
| const int * | UnsafePointer<Int32> |
| NSDate** | AutoreleasingUnsafeMutablePointer<NSDate> |
| struct UnknownType * | COpaquePointer |
通用的規(guī)則是,可變的指針變量指向可變的變量,在第三個(gè)示例中,指向?qū)ο笾羔樀闹羔槺晦D(zhuǎn)換為 AutoreleasingUnsafeMutablePointer 。
然而,如果指向的類型沒有完全定義或不能在 Swift 中表示,這種指針將會(huì)被轉(zhuǎn)換為 COpaquePointer (在 Swift 3.0 中,將會(huì)簡(jiǎn)化為 OpaquePointer ),一種沒有類型的指針,特別是只包含一些位(bits)的結(jié)構(gòu)體。 COpaquePointer 指向的值不能被直接訪問,指針變量首先需要轉(zhuǎn)換才能使用。
UnsafeMutablePointer 類型會(huì)自動(dòng)轉(zhuǎn)換為 UnsafePointer<Type> (比如當(dāng)你傳入一個(gè)可變的指針到一個(gè)需要不可變指針的函數(shù)中時(shí)),反過來轉(zhuǎn)換的話,將會(huì)出現(xiàn)編譯錯(cuò)誤。一個(gè)指向不可變值的指針,不能被轉(zhuǎn)換成一個(gè)指向可變值的指針,在這種情況下,Swift 會(huì)保證最小的安全性。
類名稱帶有unsafe字眼代表了我們?nèi)绾稳ピL問內(nèi)容,但是指向的對(duì)象的生命周期是怎么樣的,我們應(yīng)該如何處理,難道是通過 ARC 嗎?
我們已經(jīng)知道,Swift 使用 ARC 來管理引用類型的生命周期(一些結(jié)構(gòu)體和枚舉類型包含引用類型時(shí),也會(huì)被管理起來。)并且跟蹤宿主,那么 UnsafePointers 的行為是通過一些特有的方式進(jìn)行的嗎?
答案是否定的,如果 UnsafePointer<Type> 結(jié)構(gòu)體指向的是一個(gè)引用類型(一個(gè)類的對(duì)象)或者包含一些被跟蹤的引用,那么 UnsafePointer<Type> 結(jié)構(gòu)體將被跟蹤。你應(yīng)該知道這些事實(shí),這會(huì)有助于去理解一些奇怪的事情,在我們后面討論內(nèi)存分配的時(shí)候會(huì)遇到。
現(xiàn)在我們已經(jīng)知道指針是如何轉(zhuǎn)換的,另外還有2個(gè)事情要說明一下:指針如何解引去獲取或者修改指向的值,以及我們?nèi)绾文塬@取一個(gè)指向新的或者已經(jīng)存在的 Swift 變量的指針。
一旦你得到一個(gè)非空的 UnsafePointer<Memory> 變量時(shí),直接使用 memory 屬性獲取或者修改指向的值(校對(duì)者注:目前 Swift3 中已改為 pointee 解引取值):
var anInt:Int = myIntPointer.memory //UnsafePointer<Int> --> Int
myIntPointer.memory = 42
myIntPointer[0] = 43
你也可以訪問同類型指針序列中的特定元素,就像你在 C 語(yǔ)言中使用數(shù)組下標(biāo)那樣,每次累加索引值,移動(dòng)到序列中下一個(gè) strideof(Memory) 長(zhǎng)度的元素位置。
另外一方面,如果你獲取一個(gè)變量的 UnsafePointer 指針,然后將其作為參數(shù)傳遞給函數(shù),只有在這種情況下,
使用 & 操作符能夠簡(jiǎn)單地將 inout 參數(shù)傳遞到函數(shù)中:
let i = 42
functionThatNeedsAPointer(&i)
考慮到操作符不能運(yùn)用在那些描述過的函數(shù)調(diào)用上下文之外的轉(zhuǎn)換,如果你需要獲取一個(gè)指針變量做進(jìn)一步的計(jì)算(例如指針類型轉(zhuǎn)換), Swift 提供了 2 個(gè)工具函數(shù) withUnsafePointer 和 withUnsafeMutablePointer :
withUnsafePointer(&i, { (ptr: UnsafePointer<Int>) -> Void in
var vptr= UnsafePointer<Void>(ptr)
functionThatNeedsAVoidPointer(vptr)
})
let r = withUnsafePointer(&i, { (ptr: UnsafePointer<Int>) -> Int in
var vptr = UnsafePointer<Void>(ptr)
return functionThatNeedsAVoidPointerAndReturnsInt(vptr)
})
這個(gè)函數(shù)創(chuàng)建了一個(gè)給定變量的指針對(duì)象,把它傳入給一個(gè)閉包,閉包使用它然后返回一個(gè)值。在閉包作用域里面,指針能夠保證一直有效,可以認(rèn)為只能在閉包的上下文中使用,不能返回給外部的作用域。
這種方式使得訪問變量可能引發(fā)的不安全性被限制在一個(gè)定義良好的閉包作用域中。在上面的例子中,我們?cè)趥鬟f這個(gè)參數(shù)給函數(shù)之前,把整型指針轉(zhuǎn)換為了void指針。要感謝 UnsafePointer 類的構(gòu)造函數(shù)可以直接做這種指針之間的轉(zhuǎn)換。
接下來讓我們簡(jiǎn)單看看之前的 COpaquePointer , ,關(guān)于COpaquePointer ,沒有特別的地方,它可以很容易地轉(zhuǎn)換成一個(gè)給定類型的指針,然后使用 memory 屬性來訪問值,就像其他的UnsafePointer一樣。
// ptr is an untyped COpaquePointer
var iptr: UnsafePointer<Int>(ptr)
print(iptr.memory)
現(xiàn)在讓我們回到本文開頭定義的那個(gè)字符數(shù)組上來,根據(jù)我們目前掌握的知識(shí)點(diǎn),知道一個(gè) CChar 的元組可以自動(dòng)轉(zhuǎn)換成一個(gè)指向 CChar 序列的指針,這樣可以輕松地把這個(gè)元組轉(zhuǎn)換成字符串:
let namestr = withUnsafePointer(&name, { (ptr) -> String? in
let charPtr = UnsafeMutablePointer<CChar>(ptr)
return String.fromCString(charPtr)
})
print(namestr!) //IA#AString
我們可以使用其他方式獲得一個(gè)指向典型 Swift 數(shù)組的指針,然后調(diào)用某個(gè)方法將其轉(zhuǎn)換成 UnsafeBufferPointer :
let array: [Int8] = [ 65, 66, 67, 0 ]
puts(array) // ABC
array.withUnsafeBufferPointer { (ptr: UnsafeBufferPointer<Int8>) in
puts(ptr.baseAddress + 1) //BC
}
請(qǐng)注意 UnsafeBufferPointer 可以使用 baseAddress 屬性,這個(gè)屬性包含了緩沖區(qū)的基本地址。
還有另外一個(gè)類型的指針我們還沒有討論:函數(shù)指針。從 Swift 2.0開始,C 函數(shù)指針被導(dǎo)入為閉包,使用一個(gè)特殊的屬性標(biāo)記 @convention(c) ,表示這個(gè)閉包遵從 C 調(diào)用約定,我們將在接下來的某個(gè)章節(jié)解釋其具體的含義。
請(qǐng)暫時(shí)忽略具體的實(shí)現(xiàn)細(xì)節(jié),你只需了解函數(shù)指針的基本知識(shí):每導(dǎo)入一個(gè) C 函數(shù),如果需要將函數(shù)指針作為參數(shù)傳入時(shí),會(huì)使用一個(gè)內(nèi)置定義的閉包,或者一個(gè) Swift 函數(shù)引用(就像其他指針一樣,nil 也是允許的)作為參數(shù)。
<a name="allocating_memory"></a>
內(nèi)存分配
到現(xiàn)在為止,我們僅使用指針指向已經(jīng)存在的 Swift 對(duì)象,但是并沒有手動(dòng)分配過內(nèi)存。在這個(gè)章節(jié)中,我們將會(huì)學(xué)習(xí)如何在 Swift 中使用推薦的方式進(jìn)行內(nèi)存分配,或者就如我們?cè)?C 語(yǔ)言中所做的那樣,使用malloc系列函數(shù)完成內(nèi)存分配(可能在一些特定情況下非常有用)。
在開始之前,我們需要意識(shí)到 UnsafePointers 和古老的 C 指針一樣,在它們的生命周期中存在 3 種可能的狀態(tài):
- 未分配的:沒有預(yù)留的內(nèi)存分配給指針
- 已分配的:指針指向一個(gè)有效的已分配的內(nèi)存地址,但是值沒有被初始化。
- 已初始化:指針指向已分配和已初始化的內(nèi)存地址。
指針將根據(jù)我們具體的操作在這 3 個(gè)狀態(tài)之間進(jìn)行轉(zhuǎn)換。
大多數(shù)情況下,推薦你使用 UnsafePointer 類提供處理指針的方法分配一個(gè)新的對(duì)象,然后獲取指向這個(gè)實(shí)例的指針,并進(jìn)行初始化?操作,一旦使用完畢,清空它的內(nèi)容并釋放它指向的內(nèi)存。
讓我們看看一個(gè)基本的例子:
var ptr = UnsafeMutablePointer<CChar>.alloc(10)
ptr.initializeFrom([CChar](count: 10, repeatedValue: 0))
// 對(duì)對(duì)象進(jìn)行一些操作
ptr[3] = 42
ptr.destroy() //清理
ptr.dealloc(10) //釋放內(nèi)存
這里我們使用 alloc(num: Int) 分配長(zhǎng)度為 10 的 CChars (UInt8) 內(nèi)存塊,這等同于調(diào)用 malloc 方法分配指定長(zhǎng)度的內(nèi)存,然后將內(nèi)容轉(zhuǎn)換成我們需要的特定類型。前一種方法會(huì)避免更少的錯(cuò)誤,因?yàn)槲覀儾挥萌ナ謩?dòng)指定總體長(zhǎng)度。
一旦 UnsafeMutablePointer 被分配一塊內(nèi)存后,我們必須初始化這個(gè)可變的對(duì)象,使用 initialize(value: Memory) 和 initializeFrom(value: SequenceType) 方法指定初始內(nèi)容。當(dāng)操作對(duì)象完畢,我們想釋放分配的內(nèi)存資源,首先會(huì)使用 destroy 清空內(nèi)容,然后調(diào)用 dealloc(num: Int) 方法釋放指針。
必須指出,Swift 運(yùn)行時(shí)不負(fù)責(zé)清空內(nèi)容和釋放指針,因此為一個(gè)變量分配內(nèi)存之后,一旦使用完畢,你還要肩負(fù)起釋放內(nèi)存的責(zé)任。
讓我們看看另外一個(gè)例子,這次指針指向是一個(gè)復(fù)雜的 Swift 值類型:
var ptr = UnsafeMutablePointer<String>.alloc(1)
sptr.initialize("Test String")
print(sptr[0])
print(sptr.memory)
ptr.destroy()
ptr.dealloc(1)
包括分配/初始化和清理/析構(gòu)化 2 個(gè)階段的系列操作,對(duì)于值類型和引用類型來說是一樣的。但是如果你仔細(xì)研究,你會(huì)發(fā)現(xiàn)對(duì)于相同的值類型(比如整型,浮點(diǎn)數(shù)或者一些簡(jiǎn)單結(jié)構(gòu)體),初始化過程并非必須,你可以通過 memory 屬性或者下標(biāo)來進(jìn)行初始化。
但是這種方式不適用指針指向一個(gè)類,或某些特定的結(jié)構(gòu)體和枚舉的情況。必須進(jìn)行初始化操作,這是為什么呢?
當(dāng)你使用上面提及的方式修改內(nèi)存內(nèi)容,從內(nèi)存管理角度來說,有關(guān)這種行為背后的原因和發(fā)生時(shí)有關(guān)的。讓我們來看一個(gè)不需要手動(dòng)初始化內(nèi)存的代碼片段,倘若我們?cè)跊]有初始化 UnsafePointer 情況下改變了指針指向的內(nèi)存,會(huì)引發(fā)崩潰。
struct MyStruct1{
var int1:Int
var int2:Int
}
var s1ptr = UnsafeMutablePointer<MyStruct1>.alloc(5)
s1ptr[0] = MyStruct1(int1: 1, int2: 2)
s1ptr[1] = MyStruct1(int1: 1, int2: 2) // 似乎不應(yīng)該是這樣,但是這能夠正常工作
s1ptr.destroy()
s1ptr.dealloc(5)
這里沒有問題,可以使用,讓我們看看其他例子:
class TestClass{
var aField:Int = 0
}
struct MyStruct2{
var int1:Int
var int2:Int
var tc:TestClass // 這個(gè)字段是引用類型
}
var s2ptr = UnsafeMutablePointer<MyStruct2>.alloc(5)
s2ptr.initializeFrom([MyStruct2(int1: 1, int2: 2, tc: TestClass()),
MyStruct2(int1: 1, int2: 2, tc: TestClass())]) // 刪除這行初始化代碼將引發(fā)崩潰
s2ptr[0] = MyStruct2(int1: 1, int2: 2, tc: TestClass())
s2ptr[1] = MyStruct2(int1: 1, int2: 2, tc: TestClass())
s2ptr.destroy()
s2ptr.dealloc(5)
這段代碼的作用已在前面的指針操作章節(jié)進(jìn)行了相關(guān)解釋,MyStruct2 包含一個(gè)引用類型,所以它的生命周期交由 ARC 管理。當(dāng)我們修改其中一個(gè)指向的內(nèi)存模塊值的時(shí)候,Swift 運(yùn)行時(shí)將試圖釋放之前存在的對(duì)象,由于這個(gè)對(duì)象沒有被初始化,內(nèi)存存在垃圾,你的應(yīng)用將會(huì)崩潰。
請(qǐng)牢記這一點(diǎn),從安全的角度來講,最受歡迎的初始化手段是使用 initialize 分配完成內(nèi)存后,直接設(shè)置變量的初始值。
另外一個(gè)方法來自與本節(jié)最開始的一個(gè)提示,導(dǎo)入標(biāo)準(zhǔn) C 庫(kù)(Darwin 或者 Linux 下的 Glibc),然后使用 malloc 系列函數(shù):
var ptr = UnsafeMutablePointer<CChar>(malloc(10*strideof(CChar)))
ptr[0] = 11
ptr[1] = 12
free(ptr)
你可以看到,我們并沒有使用之前推薦的方法來初始化實(shí)例,那是因?yàn)槲覀冊(cè)谧罱囊还?jié)中注明了,類似 CChar 和一些基本結(jié)構(gòu)體,更適合使用這種方式。
接下來讓我們看看兩個(gè)附加的例子來講解兩個(gè)常用的函數(shù):memcpy 和 mmap :
var val = [CChar](count: 10, repeatedValue: 1)
var buf = [CChar](count: val.count, repeatedValue: 0)
memcpy(&buf, &val, buf.count*strideof(CChar))
buf // [1,1,1,1,1,1,1,1,1,1]
let ptr = UnsafeMutablePointer<Int>(mmap(nil,
Int(getpagesize()),
PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE,
-1,
0))
ptr[0] = 3
munmap(ptr, Int(getpagesize()))
這段代碼和你使用 C 語(yǔ)言做的類似,請(qǐng)注意你可以使用 getpagesize() 輕松地獲取內(nèi)存頁(yè)的大小。
第一個(gè)例子展示我們可以使用 memcpy 來設(shè)置內(nèi)存,第二個(gè)例子展示了一個(gè)真實(shí)的用例,提供一個(gè)可選的內(nèi)存分配方法,在這里我們映射了一個(gè)新的內(nèi)存頁(yè),但是我們只是映射了一個(gè)特定的內(nèi)存區(qū)域或者說一個(gè)特定的文件指針,在這案例中,我們可以不用初始化直接訪問這里之前存在的內(nèi)容。
讓我們接下來看看來自 SwiftyGPIO 中真實(shí)的案例, 在這里我映射了一個(gè)內(nèi)存區(qū)域, 包含了樹莓派的數(shù)字 GPIO 的注冊(cè),將會(huì)被用到貫穿到整個(gè)庫(kù)的讀取和寫入值的情況。
// BCM2708_PERI_BASE = 0x20000000
// GPIO_BASE = BCM2708_PERI_BASE + 0x200000 /* GPIO controller */
// BLOCK_SIZE = 4*1024
private func initIO(id: Int){
let mem_fd = open("/dev/mem", O_RDWR|O_SYNC)
guard (mem_fd > 0) else {
print("Can't open /dev/mem")
abort()
}
let gpio_map = mmap(
nil,
BLOCK_SIZE, // Map length
PROT_READ|PROT_WRITE, // Enable read/write
MAP_SHARED, // Shared with other processes
mem_fd, // File to map
GPIO_BASE // Offset to GPIO peripheral
)
close(mem_fd)
let gpioBasePointer = UnsafeMutablePointer<Int>(gpio_map)
if (gpioBasePointer.memory == -1) { //MAP_FAILED not available, but its value is (void*)-1
print("mmap error: " + String(gpioBasePointer))
abort()
}
gpioGetPointer = gpioBasePointer.advancedBy(13)
gpioSetPointer = gpioBasePointer.advancedBy(7)
gpioClearPointer = gpioBasePointer.advancedBy(10)
inited = true
}
當(dāng)映射從 0x20200000 開始的 4KB 區(qū)域后,我們獲得三個(gè)感興趣的寄存器地址,之后可以通過內(nèi)存屬性來讀取或者寫入這些值了。
<a name="pointer_arithmetic"></a>
指針計(jì)算
使用指針運(yùn)算來移動(dòng)序列或者獲取一個(gè)復(fù)雜變量特定成員的引用,在 C 語(yǔ)言中非常常見,我們可以在 Swift 做到嗎?
當(dāng)然可以,UnsafePointer 和它的可變變量,提供了一些方便的方法,允許像 C 語(yǔ)言那樣對(duì)指針使用增加或者修改的計(jì)算操作:
successor() , predecessor() , advancedBy(positions:Int) 和 distanceTo(target:UnsafePointer<T>) 。
var aptr = UnsafeMutablePointer<CChar>.alloc(5)
aptr.initializeFrom([33,34,35,36,37])
print(aptr.successor().memory) // 34
print(aptr.advancedBy(3).memory) // 36
print(aptr.advancedBy(3).predecessor().memory) // 35
print(aptr.distanceTo(aptr.advancedBy(3))) // 3
aptr.destroy()
aptr.dealloc(5)
但是說老實(shí)話,即使我提前展示了這些方法,并且這些是我推薦給你使用的方法,但是還是可以增加或者減少一個(gè) UnsafePointer (不是很 Swift 化),來得到指針從而獲得序列中的其他元素:
print((aptr+1).memory) // 34
print((aptr+3).memory) // 36
print(((aptr+3)-1).memory) // 35
<a name="working_with_strings"></a>
字符串操作
我們現(xiàn)在已經(jīng)知道,當(dāng)一個(gè) C 函數(shù)有一個(gè) char 指針的參數(shù)時(shí),這個(gè)參數(shù)將在 Swift 被轉(zhuǎn)換成 UnsafePointer<Int8> ,但是自從 Swift 可以自動(dòng)地將字符串轉(zhuǎn)換 UTF8 緩存的指針后,你也可以使用字符串作為指針調(diào)用這些函數(shù),而不需要提前手動(dòng)進(jìn)行轉(zhuǎn)換。
另外,如果你在調(diào)用一個(gè)需要 char 指針的函數(shù)之前,需要對(duì)這個(gè)指針進(jìn)行附加的操作,Swift 的字符串提供了 withCString 方法,傳入一個(gè) UTF8 字符緩存給一個(gè)閉包,這個(gè)閉包返回一個(gè)可選值。
puts("Hey! I was a Swift string!") // 傳入 Swift 字符串到 C 函數(shù)中
var testString = "AAAAA"
testString.withCString { (ptr: UnsafePointer<Int8>) -> Void in
// Do something with ptr
functionThatExpectsAConstCharPointer(ptr)
}
可以直接把一個(gè) C 字符串轉(zhuǎn)換成一個(gè) Swift 字符串,只需要使用 String 靜態(tài)方法 fromCString ,需要注意的是,C 字符串必須有空終止字符串。(譯者注:字符串以 "\0" 結(jié)束)。
let swiftString = String.fromCString(aCString)
如果你想在 Swift 中植入一些 C 代碼,用來處理字符串,比如處理用戶輸入,你可能有需求比較字符串中每個(gè)字符和一個(gè)單獨(dú)的 ASCII碼或者一個(gè)ASCII返回,這些操作,能在把字符串設(shè)計(jì)為結(jié)構(gòu)體的 Swift 代碼中實(shí)現(xiàn)嗎?
答案是肯定的,但是我不在這里對(duì) Swift 的字符串展開深入的探討,如果你想學(xué)到更多關(guān)于 Swift 是結(jié)構(gòu)體的知識(shí)點(diǎn),請(qǐng)查看Ole Begemann和Andy Bargh的文章獲取更多的知識(shí)。
下面看一個(gè)例子,我們定義了一個(gè)函數(shù),判斷一個(gè)字符串是否只由基本可以打印的 ASCII 字符組成,這樣我們可以在 C 的代碼中使用這個(gè)字符串:
func isPrintable(text:String)->Bool{
for scalar in text.unicodeScalars {
let charCode = scalar.value
guard (charCode>31)&&(charCode<127) else {
return false // Unprintable character
}
}
return true
}
在 C 中,字符整型值和一個(gè) ASCII 組成的字符串中的每個(gè)字符之間的比較,換到 Swift 代碼中并沒有改變很多,是使用的每個(gè)字符串的 unicode 值進(jìn)行的比較。需要注意的是。需要明確的是,這個(gè)方法只能在字符串是由單個(gè)標(biāo)量單位支持時(shí)候有用,不是通用的。
那么在字符和他們的數(shù)字 ascii 值之間如何進(jìn)行轉(zhuǎn)換呢?
為了轉(zhuǎn)換一個(gè)數(shù)字為對(duì)應(yīng)的 字符 或者 字符串 時(shí),我們首先要把它轉(zhuǎn)換成 UnicodeScalar ,然后更加緊湊的方式是使用 UInt8 提供的特定的構(gòu)造函數(shù):
let c = Character(UnicodeScalar(70)) // "F"
let s = String(UnicodeScalar(70)) // "F"
let asciiForF = UInt8(ascii:"F") // 70
上面例子中的 guard 語(yǔ)句可以改成 UInt8(ascii:) 增加可讀性。
<a name="working_with_functions"></a>
函數(shù)操作
在字符串一節(jié)我們可以看到,Swift 自動(dòng)將作為參數(shù)的 C 函數(shù)指針變成閉包,但是有一個(gè)主要的缺點(diǎn)是,閉包被用作 C 函數(shù)指針參數(shù)時(shí),不能捕獲任何在上下文外的值。
為了對(duì)此進(jìn)行約束,這種類型的閉包(這種閉包是從 C 函數(shù)指針轉(zhuǎn)換而來),被自動(dòng)的加上一個(gè)特定特定類型屬性@convention(c), 在 Swift 語(yǔ)言參考中類型屬性章節(jié)中有詳細(xì)描述,表示調(diào)用時(shí)候閉包必須遵從的約定,可能的值有: c , objc 和 swift 。
另外存在一個(gè)可選的方案來解決這個(gè)限制,在 Chris Eidhof 的這篇文章中可以看到,使用一個(gè)基于代碼塊(block-based)函數(shù),如果你是在一個(gè)基于 Darwin 的系統(tǒng)上調(diào)用一個(gè)函數(shù)就會(huì)有一個(gè)代碼塊的變量,傳入一個(gè)保持環(huán)境的對(duì)象到函數(shù)中,同時(shí)遵守了常見的 C 模式。
接下來我們簡(jiǎn)要說說可變參數(shù)函數(shù)。
Swift 不支持傳統(tǒng)的 C 可變參數(shù)函數(shù),可以肯定的是,在你第一次試圖調(diào)用類似于printf之類的可變參數(shù)函數(shù)時(shí),Swift 將在編譯時(shí)就報(bào)錯(cuò)。如果你真的需要調(diào)用它們,唯一可行的方案是創(chuàng)建一個(gè) C 的包裹函數(shù),限制參數(shù)的數(shù)量或者使用va_list(Swift 支持)來間接接受多個(gè)參數(shù)。
所以,即使 printf 不能工作,但是 vprintf 或者其他支持 va_list 的函數(shù)可以在 Swift 中工作。
為了把數(shù)組參數(shù)或者一個(gè)可變的 Swift 參數(shù)列表轉(zhuǎn)換為 va_list 指針,每一個(gè)參數(shù)必須實(shí)現(xiàn) CVarArgType ,然后你只需要調(diào)用 withVaList 來獲取 CVaListPointer ,這個(gè)指針指向你的參數(shù)列表( getVaList 也可以用但是文檔推薦盡量不使用它)。讓我們看看一個(gè)使用 vprintf 的例子:
withVaList(["a", "b", "c"]) { ptr -> Void in
vprintf("Three strings: %s, %s, %s\n", ptr)
}
<a name="unmanaged"></a>
Unmanaged
我們已經(jīng)或多或少了解有關(guān)指針的知識(shí)點(diǎn),但仍然不可避免存在一些我們已知卻無(wú)法處理的事項(xiàng)。
如果我們把一個(gè) Swift 引用對(duì)象作為參數(shù),傳遞給一個(gè)在回調(diào)中返回結(jié)果的函數(shù)中,會(huì)怎么樣呢?我們能保證,在切換上下文時(shí),Swift 對(duì)象仍然在哪里,而 ARC 沒有釋放它嗎?答案是不能,我們不能做假設(shè),這個(gè)對(duì)象仍然存在在哪里。
使用 Unmanaged ,使用一個(gè)帶有一些有趣的工具方法的類,來解決上面我們提到的情況。帶有 Unmanaged 你可以改變對(duì)象的引用計(jì)數(shù),在你需要它的時(shí)候轉(zhuǎn)換為COpaquePointer。
讓我們來看一個(gè)實(shí)際的案例,這里有一個(gè)前面我們描述有這個(gè)特性的 C 函數(shù):
c
// cstuff.c
void aCFunctionWithContext(void* ctx, void (*function)(void* ctx)){
sleep(3);
function(ctx);
}
然后使用 Swift 代碼來調(diào)用它:
class AClass : CustomStringConvertible {
var aProperty:Int=0
var description: String {
return "A \(self.dynamicType) with property \(self.aProperty)"
}
}
var value = AClass()
let unmanaged = Unmanaged.passRetained(value)
let uptr = unmanaged.toOpaque()
let vptr = UnsafeMutablePointer<Void>(uptr)
aCFunctionWithContext(vptr){ (p:UnsafeMutablePointer<Void>) -> Void in
var c = Unmanaged<AClass>.fromOpaque(COpaquePointer(p)).takeUnretainedValue()
c.aProperty = 2
print(c) //A AClass with property 2
}
使用 passRetained 和 passUnretained 方法, Unmanaged 保持了一個(gè)給定的對(duì)象,對(duì)應(yīng)的增加或者不增加它的引用計(jì)數(shù)。
因?yàn)榛卣{(diào)需要一個(gè) void 指針,我們首先使用 toOpaque() 獲取 COpaquePointer ,然后把它轉(zhuǎn)換為 UnsafeMutablePointer<Void> 。
在回調(diào)中,我們做了相反的轉(zhuǎn)換,獲取到指向原始類的引用,然后修改它的值。
我們從未管理的對(duì)象提取出類,我們可以使用 takeRetainedValue 或者 takeUnretainedValue ,使用上面描述的相似的手法,對(duì)應(yīng)地減少或者取消未修改的值的引用計(jì)數(shù)。
在這個(gè)例子中,我們沒有減少引用計(jì)數(shù),所以即使跳出了閉包的范圍,這個(gè)類也不會(huì)被釋放。這個(gè)類將通過未管理的實(shí)例中進(jìn)行手動(dòng)釋放。
這只是一個(gè)簡(jiǎn)單的,或許不是最好的案例,用來表示 Unmanaged 可以解決的一系列問題,想要獲取更多的Unmanaged信息,請(qǐng)查看 NSHipster 的文章。
<a name="working_with_files"></a>
文件操作
在一些平臺(tái)上,我們可以直接使用標(biāo)準(zhǔn) C 語(yǔ)言庫(kù)中的函數(shù)處理文件,讓我們看看一些讀取文件的例子吧:
let fd = fopen("aFile.txt", "w")
fwrite("Hello Swift!", 12, 1, fd)
let res = fclose(file)
if res != 0 {
print(strerror(errno))
}
let fd = fopen("aFile.txt", "r")
var array = [Int8](count: 13, repeatedValue: 0)
fread(&array, 12, 1, fd)
fclose(fd)
let str = String.fromCString(array)
print(str) // Hello Swift!
從上面的代碼你可以看到,關(guān)于文件訪問沒有什么奇怪的或者復(fù)雜的操作,這段代碼和你使用 C 語(yǔ)言編碼是差不多的。需要注意的是我們可以完全獲取錯(cuò)誤信息和使用相關(guān)的函數(shù)。
<a name="bitwise_operations"></a>
位操作
當(dāng)你和 C 進(jìn)行互調(diào)時(shí)候,有很大的可能會(huì)進(jìn)行一些位操作,我推薦一篇之前寫的文章,覆蓋到了這方面你想了解的知識(shí)點(diǎn)。
<a name="swift_and_c_mixed_projects"></a>
Swift 和 C 的混合項(xiàng)目
Swift 項(xiàng)目可以使用一個(gè)橋接的頭文件來訪問 C 庫(kù), 這個(gè)做法與使用 Objective-C 庫(kù)是類似的。
但是這種方法不能用在框架項(xiàng)目中,所以我們采用一個(gè)更通用的替代方法,不過需要一些簡(jiǎn)單的配置。我們將創(chuàng)建一個(gè) LLVM 模塊,其中包含一些我們要導(dǎo)入到 Swift 的 C 代碼。
假設(shè)我們已經(jīng)在 Swift 項(xiàng)目中添加了 C 代碼的源文件:
c
// CExample.c
#include "CExample.h"
#include <stdio.h>
void printStuff(){
printf("Printing something!\n");
}
void giveMeUnsafeMutablePointer(int* param){ }
void giveMeUnsafePointer(const int * param){ }
和對(duì)應(yīng)的頭文件:
c
// CExample.h
#ifndef CExample_h
#define CExample_h
#include <stdio.h>
#define IAMADEFINE 42
void printStuff();
void giveMeUnsafeMutablePointer(int* param);
void giveMeUnsafePointer(const int * param);
typedef struct {
char name[5];
int value;
} MyStruct;
char name[] = "IAmAString";
char* anotherName = "IAmAStringToo";
#endif /* CExample_h */
為了區(qū)分 C 源代碼和其他代碼,我們?cè)陧?xiàng)目根目錄中建立了 CExample 文件夾,把 C 代碼文件放到里面。
我們必須在這個(gè)目錄下創(chuàng)建一個(gè) module.map 文件,然后這個(gè)文件定義了我們導(dǎo)出的 C 模塊和對(duì)應(yīng)的 C 頭文件。
c
module CExample [system] {
header "CExample.h"
export *
}
你可以看到,我們導(dǎo)出了頭文件定義的所有內(nèi)容,其實(shí)模塊可以在我們需要的時(shí)候部分導(dǎo)出。
此外,這個(gè)例子中實(shí)際的庫(kù)文件源碼已經(jīng)包含在項(xiàng)目中了,但是如果你想導(dǎo)入一個(gè)在系統(tǒng)中存在的庫(kù)到 Swift 中的話,你只需要?jiǎng)?chuàng)建一個(gè) module.map (不需要在源碼的目錄下創(chuàng)建),然后指定頭文件或者系統(tǒng)的頭文件。只是你需要在 modulemap 文件中使用link libname指令指定這個(gè)庫(kù)的頭文件名和具體的庫(kù)的關(guān)聯(lián)關(guān)系(和你手動(dòng)使用 -llibname 一樣去鏈接這個(gè)庫(kù))。然后你也可以在一個(gè) module.map 中定義多個(gè)模塊。
想學(xué)習(xí)更多的關(guān)于 LLVM 模塊和所有選項(xiàng)的信息,請(qǐng)查看官方文檔。
最后一步是把模塊目錄添加到編譯器的查詢路徑中。你需要做的是,打開項(xiàng)目屬性配置項(xiàng),在 Swift Compiler - Search Paths 下的 Import Paths 中添加模塊路徑(${SRCROOT}/CExample)
然后就這樣,我們可以導(dǎo)入這個(gè) C 模塊到 Swift 代碼中,然后使用其中的函數(shù)了:
import CExample
printStuff()
print(IAMADEFINE) //42
giveMeUnsafePointer(UnsafePointer<Int32>(bitPattern: 1))
giveMeUnsafeMutablePointer(UnsafeMutablePointer<Int32>(bitPattern: 1))
let ms = MyStruct(name: (0, 0, 0, 0, 0), value: 1)
print(ms)
print(name) // (97, 115, 100, 100, 97, 115, 100, 0)
//print(String.fromCString(name)!) // Cannot convert it
print(anotherName) //0xXXXXXX pointer address
print(String.fromCString(anotherName)!) //IAmAStringToo
<a name="closing_thoughts"></a>
結(jié)束語(yǔ)
我希望這篇文章至少能夠給你帶來心中對(duì)于探索 Swift 和 C 交互這個(gè)未知世界的一些光亮,但是我也不是期望能夠把你在項(xiàng)目過程中遇到的問題都解決掉。
你也會(huì)發(fā)現(xiàn),想把事情按照預(yù)期的方向進(jìn)行,你需要多做一些實(shí)驗(yàn)。在下個(gè)版本的 Swift 中(譯者注:指 Swift 3.0),與 C 的互調(diào)會(huì)變得更強(qiáng)。(在 Swift 2.0 才引入的 UnsafePointer 和相關(guān)的函數(shù),在這之前,和 C 的互調(diào)有一些困難)
用一個(gè)提示作為結(jié)束,關(guān)于 Swift Package Manager 和支持 Swift/C 混編項(xiàng)目,自動(dòng)生成 modulemaps 來支持導(dǎo)入 C 模塊的一個(gè) pr 在昨天進(jìn)行了合并操作,閱讀這篇文章可以看到它如何進(jìn)行工作。
本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán),最新文章請(qǐng)?jiān)L問 http://swift.gg。