Swift 和 C 不得不說的故事

作者: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)目。

GitHub或者zipped獲取 Swift/C 混合編碼的 playground。

內(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ù)而使用 strideofstrideOfValue 方法,讓我們通過一個(gè)例子來看看 sizeofstrideof 返回的區(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)下,strideofsizeof 之間返回的值會(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ù) withUnsafePointerwithUnsafeMutablePointer

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ù):memcpymmap

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

GitHub或者zipped獲取 Swift/C 混合編碼的 playground。

<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 BegemannAndy 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í)候閉包必須遵從的約定,可能的值有: cobjcswift 。

另外存在一個(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
}

使用 passRetainedpassUnretained 方法, 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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容