最近在學(xué)習(xí)中遇到到 Swift 和 C 第三方庫(kù)交互的問(wèn)題,Google 到了這篇文章順便記錄了下,文章原文出處《unsafe Swift: Using Pointers And Interacting With C》
默認(rèn)情況下,Swift 是內(nèi)存安全的,這意味著避免直接訪問(wèn)內(nèi)存,并且在使用前要確保一切都是初始化過(guò)的。當(dāng)然這是在默認(rèn)情況下,在我們需要時(shí)不安全(unsafe)Swift (以下內(nèi)容將直接用 unsafe Swift,翻譯了感覺(jué)別扭)允許我們通過(guò)指針直接訪問(wèn)內(nèi)存。
這篇教程將帶你快速領(lǐng)略所謂“unsafe"的 Swift 特征。術(shù)語(yǔ)“unsafe”有時(shí)會(huì)讓人感到迷惑,它并不是說(shuō)你寫的代碼是危險(xiǎn)的、不可運(yùn)行的爛代碼,僅僅意味著你在寫代碼時(shí)需要格外的謹(jǐn)慎,因?yàn)榫幾g器在這方面對(duì)你的幫助是有限的。
當(dāng)你要與類似 C 這樣的不安全語(yǔ)言進(jìn)行交互時(shí)就會(huì)用到這些特征,比如要?提高運(yùn)行時(shí)性能或探究 Swift 的內(nèi)部原理。雖然這是一個(gè)高級(jí)課程,但如果你有正常的 Swift 能力,你應(yīng)該能夠跟得上課程。 C 經(jīng)驗(yàn)將對(duì)你的學(xué)習(xí)有所幫助,但并不是必需的。
入門
這一課程由三個(gè) playgrounds 組成,在第一個(gè) playground,你將創(chuàng)建幾個(gè)短片段來(lái)探索內(nèi)存布局并使用不安全的指針,在第二個(gè) playground,你將使用 Swift 接口封裝一個(gè)低階的 C API 來(lái)展示數(shù)據(jù)流壓縮,最后一個(gè) playground,您將創(chuàng)建一個(gè)平臺(tái)獨(dú)立的替代 arc4random,在使用不安全的 Swift 時(shí),隱藏用戶的細(xì)節(jié)。
開始創(chuàng)建一個(gè)新的 playground,命名為 UnsafeSwift, 您可以選擇任何平臺(tái),本教程中的所有代碼都與平臺(tái)無(wú)關(guān), 確保導(dǎo)入 Foundation 框架。
內(nèi)存布局

unsafe Swift 直接與內(nèi)存系統(tǒng)配合使用,內(nèi)存可以被視為一系列的盒子(實(shí)際上是數(shù)十億個(gè)盒子),每個(gè)盒子里面都有一個(gè)數(shù)字,每一個(gè)盒子都有一個(gè)唯一的與之關(guān)聯(lián)的內(nèi)存地址,最小的尋址儲(chǔ)存單元是一個(gè)字節(jié),一個(gè)字節(jié)由 8 位(bits) 組成,8 位字節(jié)可以存儲(chǔ) 0-255 的值。處理器通常也可以有效地訪問(wèn)多于一個(gè)字節(jié)的存儲(chǔ)器的字。 例如,在 64 位系統(tǒng)上,一個(gè)字是 8 字節(jié)或 64 位長(zhǎng)度。
Swift 有一個(gè) MemoryLayout 技巧,可以告訴你程序中的類型大小和對(duì)齊方式。
將以下代碼添加到你的 playground:
MemoryLayout<Int>.size // returns 8 字節(jié) (on 64-bit)
MemoryLayout<Int>.alignment // returns 8 (on 64-bit)
MemoryLayout<Int>.stride // returns 8 (on 64-bit)
MemoryLayout<Int16>.size // returns 2
MemoryLayout<Int16>.alignment // returns 2
MemoryLayout<Int16>.stride // returns 2
MemoryLayout<Bool>.size // returns 1
MemoryLayout<Bool>.alignment // returns 1
MemoryLayout<Bool>.stride // returns 1
MemoryLayout<Float>.size // returns 4
MemoryLayout<Float>.alignment // returns 4
MemoryLayout<Float>.stride // returns 4
MemoryLayout<Double>.size // returns 8
MemoryLayout<Double>.alignment // returns 8
MemoryLayout<Double>.stride // returns 8
MemoryLayout <Type> 是在編譯時(shí)評(píng)估的通用類型,用于確定每個(gè)指定類型的大小,對(duì)齊方式和步幅,返回的數(shù)字是對(duì)應(yīng)數(shù)據(jù)類型的字節(jié)數(shù)。 例如,Int16 的大小是兩個(gè)字節(jié),并且也有兩個(gè)對(duì)齊方式,這意味著它必須從偶數(shù)地址開始(可以被 2 整除)。
因此,例如,給一個(gè) Int16 數(shù)據(jù)類型分配的地址是 100,那就是合法的,但如果分配的是 101 就不合法了,因?yàn)樗`反了對(duì)齊方式的要求。當(dāng)你將一堆的 Int16 類型數(shù)據(jù)放在一起,他們將以一定的間隔(步幅)打包在一起,對(duì)于這些基本類型,大小與間隔(步幅)都是相同。
接下來(lái),查看一些自定義的結(jié)構(gòu)體的布局,并將以下內(nèi)容添加到 playground 中:
struct EmptyStruct {}
MemoryLayout<EmptyStruct>.size // returns 0
MemoryLayout<EmptyStruct>.alignment // returns 1
MemoryLayout<EmptyStruct>.stride // returns 1
struct SampleStruct {
let number: UInt32
let flag: Bool
}
MemoryLayout<SampleStruct>.size // returns 5
MemoryLayout<SampleStruct>.alignment // returns 4
MemoryLayout<SampleStruct>.stride // returns 8
空結(jié)構(gòu)體的大小為 0,它可以放在任何地址,因?yàn)樗鼘?duì)齊方式是 1(即所有數(shù)字都可以被 1 整除)。奇怪的是步幅也是 1,這是因?yàn)槟銊?chuàng)建的每個(gè) EmptyStruct 必須具有唯一的內(nèi)存地址,盡管大小為零。
對(duì)于 SampleStruct,大小是 5,但間隔(步幅)卻是 8,這是由其對(duì)齊方式以 4 個(gè)字節(jié)為邊界決定的,考慮到這,最好情況下 Swift 能做到以 8 個(gè)字節(jié)間隔打包。
接下來(lái)添加:
class EmptyClass {}
MemoryLayout<EmptyClass>.size // returns 8 (on 64-bit)
MemoryLayout<EmptyClass>.stride // returns 8 (on 64-bit)
MemoryLayout<EmptyClass>.alignment // returns 8 (on 64-bit)
class SampleClass {
let number: Int64 = 0
let flag: Bool = false
}
MemoryLayout<SampleClass>.size // returns 8 (on 64-bit)
MemoryLayout<SampleClass>.stride // returns 8 (on 64-bit)
MemoryLayout<SampleClass>.alignment // returns 8 (on 64-bit)
由于類都是引用類型,所以 MemoryLayout 得到的引用類型大小都是 8 個(gè)字節(jié),如果你想了解更詳細(xì)的內(nèi)存布局,請(qǐng)看this excellent talk by Mike Ash。
指針
指針指向(封裝)的是一個(gè)內(nèi)存地址,那些涉及到直接訪問(wèn)內(nèi)存地址的類型都會(huì)有“unsafe”前綴,因此,指針類型也被稱作 UnsafePointer。雖然額外的輸入可能看起來(lái)很煩人,但它讓你和你的讀者知道你正在將內(nèi)存中的非編譯器檢查的訪問(wèn)權(quán)限浸入到未正確使用的操作中,從而導(dǎo)致不可預(yù)測(cè)的行為(而不僅僅是可預(yù)測(cè)的崩潰)。
Swift 的設(shè)計(jì)師可以僅僅創(chuàng)建單一 UnsafePointer 類型,并將其作為 char * 的 C 等價(jià)物,可以以非結(jié)構(gòu)化的方式訪問(wèn)內(nèi)存,但他們沒(méi)有這樣做,相反,Swift 包含幾十種指針類型,每種類型具有不同的功能和目的。使用適當(dāng)?shù)闹羔橆愋涂梢苑乐瑰e(cuò)誤的發(fā)生并且更清晰地表達(dá)開發(fā)者的意圖,讓你控制不可預(yù)測(cè)行為的產(chǎn)生。
Unsafe Swift 指針使用可預(yù)測(cè)的命名方案,以便你知道指針的特征是什么??勺兓蛘卟豢勺儯╮aw)或者有類型的,是否是緩沖(buffer)類型,這三種特性總共組合出了 8 種指針類型。

在以下部分中,你將了解有關(guān)這些指針類型的更多信息。
使用原生的指針
將以下代碼添加到你的 playground:
// 1
let count = 2
let stride = MemoryLayout<Int>.stride
let alignment = MemoryLayout<Int>.alignment
let byteCount = stride * count
// 2
do {
print("Raw pointers")
// 3
let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
// 4
defer {
pointer.deallocate(bytes: byteCount, alignedTo: alignment)
}
// 5
pointer.storeBytes(of: 42, as: Int.self)
pointer.advanced(by: stride).storeBytes(of: 6, as: Int.self)
pointer.load(as: Int.self)
pointer.advanced(by: stride).load(as: Int.self)
// 6
let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount)
for (index, byte) in bufferPointer.enumerated() {
print("byte \(index): \(byte)")
}
}
在這個(gè)例子里你使用 Unsafe Swift 指針存儲(chǔ)和讀取了兩個(gè)整型數(shù)據(jù),接下來(lái)說(shuō)明下它們是什么發(fā)生的:
- 這些常量會(huì)經(jīng)常被用到:
- count 表示整型數(shù)據(jù)存儲(chǔ)的個(gè)數(shù)
- stride 表示 Int 類型的步幅(即占用的空間跨度)
- alignment 表示 Int 類型的對(duì)齊方式
- byteCount 表示總共需要的字節(jié)數(shù)
- 添加一個(gè) do 代碼塊,控制代碼作用域,因此你可以做接下來(lái)的例子中重用變量名
- 方法 UnsafeMutableRawPointer.allocate 是用來(lái)分配所需的字節(jié)數(shù),它返回的是一個(gè) UnsafeMutableRawPointer 類型,通過(guò)名字你就可以知道這個(gè)指針是可以讀取和可存儲(chǔ)原生字節(jié)數(shù)的。(即可變的)
- 添加 defer 代碼塊是確保指針得到正確的釋放,ARC 在這種情況下是幫不了你的,你得自己管理內(nèi)存,關(guān)于 defer 你可以閱讀這篇文章。
- 方法 storeBytes 和 load 分別是用來(lái)存儲(chǔ)和讀取字節(jié)數(shù)的,第二個(gè)整數(shù)的存儲(chǔ)器地址是通過(guò)推進(jìn)指針步幅字節(jié)數(shù)來(lái)計(jì)算的。
由于指針是具有跨域能力的,你也可以通過(guò)指針運(yùn)算來(lái)調(diào)用 (pointer + stride).storeBytes(of: 6, as: Int.self) 。 - UnsafeRawBufferPointer 讓你訪問(wèn)內(nèi)存就像是一個(gè)字符集一樣,這意味著你可以迭代字節(jié),可以使用下標(biāo)訪問(wèn)它們,甚至使用酷的方法,如過(guò) filter,map 和 reduce 。緩沖區(qū)指針使用了原生指針進(jìn)行初始化。
使用類型指針
前面的例子可以通過(guò)使用類型指針進(jìn)行簡(jiǎn)化,將以下代碼加到你的 playground:
do {
print("Typed pointers")
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
pointer.initialize(to: 0, count: count)
defer {
pointer.deinitialize(count: count)
pointer.deallocate(capacity: count)
}
pointer.pointee = 42 // pointer 指向的內(nèi)存地址存放數(shù)值 42
pointer.advanced(by: 1).pointee = 6 // pointer 下一個(gè)內(nèi)存地址存放數(shù)值 6,即 pointer 指向的起始地址加 Int 類型的步幅再移動(dòng) 1 位,就其起始地址
pointer.pointee
pointer.advanced(by: 1).pointee
let bufferPointer = UnsafeBufferPointer(start: pointer, count: count)
for (index, value) in bufferPointer.enumerated() {
print("value \(index): \(value)")
}
}
注意以下不同點(diǎn):
- 使用 UnsafeMutablePointer.allocate 分配內(nèi)存,泛型參數(shù)(即<T>中的類型)讓 Swift 知道指針將用于加載和存儲(chǔ) Int 類型的值。
- 類型內(nèi)存必須在使用前初始化,并在使用完之后銷毀,這一操作分別是由 initialize 和 deinitialize方法來(lái)完成。備注 : 正如用戶 atrick 在下面的評(píng)論中所指出的,初始化只適用于非基礎(chǔ)數(shù)據(jù)類型。也就是說(shuō),包含銷毀過(guò)程對(duì)于你將來(lái)校對(duì)代碼是一種好的方式。通常情況下它是不會(huì)有什么不好的結(jié)果出現(xiàn)的,因?yàn)榫幾g器會(huì)對(duì)它進(jìn)行優(yōu)化。
- 類型指針有一個(gè) pointee 屬性提供了類型安全的方式來(lái)讀取和存儲(chǔ)值。
- 當(dāng)需要指針前進(jìn)的時(shí)候,我們只需要指定想要前進(jìn)的個(gè)數(shù),類型指針會(huì)自動(dòng)根據(jù)它所指向的數(shù)值類型來(lái)計(jì)算要間隔的值。同樣的,我們可以直接對(duì)指針進(jìn)行算術(shù)運(yùn)算 (pointer + 1).pointee = 6 。
- 有類型的緩沖型指針也會(huì)直接操作數(shù)值,而非字節(jié)。
轉(zhuǎn)換原生指針到類型指針
類型指針并不總是通過(guò)直接初始化得到,它們也可以從原生指針派生而來(lái)。
添加以下代碼到你的 playground:
do {
print("Converting raw pointers to typed pointers")
let rawPointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
defer {
rawPointer.deallocate(bytes: byteCount, alignedTo: alignment)
}
let typedPointer = rawPointer.bindMemory(to: Int.self, capacity: count)
typedPointer.initialize(to: 0, count: count)
defer {
typedPointer.deinitialize(count: count)
}
typedPointer.pointee = 42
typedPointer.advanced(by: 1).pointee = 6
typedPointer.pointee
typedPointer.advanced(by: 1).pointee
let bufferPointer = UnsafeBufferPointer(start: typedPointer, count: count)
for (index, value) in bufferPointer.enumerated() {
print("value \(index): \(value)")
}
}
這個(gè)例子和上一個(gè)例子是非常的相似的,除了剛開始創(chuàng)建的原生指針之外,例子中的類型指針是通過(guò)綁定 Int 類型所需的內(nèi)存來(lái)創(chuàng)建的,這種內(nèi)存綁定是類型安全的,當(dāng)你創(chuàng)建一個(gè)類型化的指針時(shí),內(nèi)存綁定是在幕后完成的。
本示例的其余部分與前一個(gè)相同, 一旦你在類型化的指針土地,你可以像例子那樣使用pointee。
獲取實(shí)例的字節(jié)數(shù)
你常常會(huì)遇到對(duì)已存在某數(shù)據(jù)類型實(shí)例做字節(jié)數(shù)檢查,這時(shí)你可以通過(guò)調(diào)用 withUnsafeBytes(of:) 方法獲得。
將以下代碼添加到你的 playground:
do {
print("Getting the bytes of an instance")
var sampleStruct = SampleStruct(number: 25, flag: true)
withUnsafeBytes(of: &sampleStruct) { bytes in
for byte in bytes {
print(byte)
}
}
}
這將會(huì)打印出 SampleStruct 實(shí)例的原生字節(jié)數(shù),withUnsafeBytes(of:) 方法可以訪問(wèn)到 UnsafeRawBufferPointer 并傳入閉包中供你使用。
withUnsafeBytes 也可以對(duì) Array 和 Data 實(shí)例使用。
校驗(yàn)和計(jì)算
使用 withUnsafeBytes(of:) 你可以得到一個(gè)返回值,以下例子使用它來(lái)計(jì)算 32 位操作系統(tǒng)結(jié)構(gòu)體字節(jié)的校驗(yàn)和。( checksum,使用少量的數(shù)據(jù)來(lái)驗(yàn)證數(shù)據(jù)在傳輸或者存儲(chǔ)時(shí)是否存在錯(cuò)誤;通常情況下,是用來(lái)檢查數(shù)據(jù)的完整性,但是不保證數(shù)據(jù)的準(zhǔn)確性可靠性)
將以下代碼添加到你的 playground:
do {
print("Checksum the bytes of a struct")
var sampleStruct = SampleStruct(number: 25, flag: true)
let checksum = withUnsafeBytes(of: &sampleStruct) { (bytes) -> UInt32 in
return ~bytes.reduce(UInt32(0)) { $0 + numericCast($1) }
}
print("checksum", checksum) // prints checksum 4294967269
}
通過(guò) reduce 調(diào)用將所有字節(jié)相加,然后使用 ~ 按位取反,這不是一個(gè)特別強(qiáng)大的錯(cuò)誤檢測(cè),但它體現(xiàn)了這個(gè)概念。
Unsafe 俱樂(lè)部的三大原則
你在寫不安全代碼時(shí)需要格外小心,以避免不可預(yù)知的行為,這里有一些爛代碼的例子。
不要讓 withUnsafeBytes 返回指針
// Rule #1
do {
print("1. Don't return the pointer from withUnsafeBytes!")
var sampleStruct = SampleStruct(number: 25, flag: true)
let bytes = withUnsafeBytes(of: &sampleStruct) { bytes in
return bytes // strange bugs here we come !
}
print("Horse is out of the barn!", bytes) /// undefined !!!
}
你不應(yīng)該在 withUnsafeBytes(of:) 閉包中出現(xiàn)逃逸指針,這一刻可能能正常執(zhí)行,但······(下一刻可能就執(zhí)行不了了)
一次只綁定一種類型
// Rule #2
do {
print("2. Only bind to one type at a time!")
let count = 3
let stride = MemoryLayout<Int16>.stride
let alignment = MemoryLayout<Int16>.alignment
let byteCount = count * stride
let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
let typedPointer1 = pointer.bindMemory(to: UInt16.self, capacity: count)
// Breakin' the Law... Breakin' the Law (Undefined behavior)
let typedPointer2 = pointer.bindMemory(to: Bool.self, capacity: count * 2)
// If you must, do it this way:
typedPointer1.withMemoryRebound(to: Bool.self, capacity: count * 2) { (boolPointer: UnsafeMutablePointer<Bool>) in
print(boolPointer.pointee) // See Rule #1, don't return the pointer
}
}

不要一次將內(nèi)存綁定到兩個(gè)不相關(guān)的類型上,這叫做類型歧義,Swift 不支持這樣的歧義。然而,你可以使用 withMemoryRebound(to:capacity:) 方法臨時(shí)重新綁定內(nèi)存。另外,這一規(guī)則表示從一個(gè)基本數(shù)據(jù)類型(例如 __Int __)重新綁定到一個(gè)非基本數(shù)據(jù)類型(例如 class),不要這樣做。
不要越界操作...哎呀!
// Rule #3... wait
do {
print("3. Don't walk off the end... whoops!")
let count = 3
let stride = MemoryLayout<Int16>.stride
let alignment = MemoryLayout<Int16>.alignment
let byteCount = count * stride
let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount + 1) // OMG +1????
for byte in bufferPointer {
print(byte) // pawing through memory like an animal
}
}
隨著不安全代碼使用,錯(cuò)誤的問(wèn)題會(huì)一個(gè)接一個(gè)呈現(xiàn)出來(lái),這太糟糕,所以使用時(shí)要謹(jǐn)慎小心,要進(jìn)行審查和測(cè)試。
Unsafe Swift 例子1:壓縮
使用你所掌握的知識(shí)封裝 C API,在 Cocoa 中引用 C 模塊實(shí)現(xiàn)一些通用的數(shù)據(jù)壓縮算法,這些包括速度至關(guān)重要的 LZ4,當(dāng)你需要最高的壓縮比并且不關(guān)心速度時(shí)的 LZ4A,能平衡空間和時(shí)間的 ZLIB,還有能夠更好的平衡空間和時(shí)間的新庫(kù)(開源庫(kù))LZFSE。
創(chuàng)建一個(gè)新的 playground,命名為 Compression,首先定義一個(gè)使用 Data 的純 Swift API。
接下來(lái),使用以下代碼來(lái)替換你 playground 中的內(nèi)容:
import Foundation
import Compression
enum CompressionAlgorithm {
case lz4 // speed is critical
case lz4a // space is critical
case zlib // reasonable speed and space
case lzfse // better speed and space
}
enum CompressionOperation {
case compression, decompression
}
// return compressed or uncompressed data depending on the operation
func perform(_ operation: CompressionOperation,
on input: Data,
using algorithm: CompressionAlgorithm,
workingBufferSize: Int = 2000) -> Data? {
return nil
}
函數(shù) perform 實(shí)現(xiàn)的是壓縮和解壓,當(dāng)前只設(shè)置返回 nil,很快你就會(huì)給它添加些 unsafe 代碼。
接下來(lái)在 playground 末尾添加以下代碼:
// together as one unit, so you never forget how the data was
// compressed.
struct Compressed {
let data: Data
let algorithm: CompressionAlgorithm
init(data: Data, algorithm: CompressionAlgorithm) {
self.data = data
self.algorithm = algorithm
}
// Compress the input with the specified algorithm. Returns nil if it fails.
static func compress(input: Data, with algorithm: CompressionAlgorithm) -> Compressed? {
guard let data = perform(.compression, on: input, using: algorithm) else {
return nil
}
return Compressed(data: data, algorithm: algorithm)
}
// Uncompressed data. Returns nil if the data cannot be decompressed.
func decompressed() -> Data? {
return perform(.decompression, on: data, using: algorithm)
}
}
結(jié)構(gòu)體 Compressed 存儲(chǔ)了要壓縮的數(shù)據(jù)和將要使用到的壓縮算法,這使得你在使用解壓縮算法時(shí)減少出錯(cuò)的可能。
接著在 playground 末尾添加以下代碼:
// For discoverability, add a compressed method to Data
extension Data {
// Returns compressed data or nil if compression fails.
func compressed(with algorithm: CompressionAlgorithm) -> Compressed? {
return Compressed.compress(input: self, with: algorithm)
}
}
// Example usage:
let input = Data(bytes: Array(repeating: UInt8(123), count: 10000))
let compressed = input.compressed(with: .lzfse)
compressed?.data.count // in most cases much less than orginal input count
let restoredInput = compressed?.decompressed()
input == restoredInput // true
這里主要的入口點(diǎn)是 Data 數(shù)據(jù)類型的擴(kuò)展,你已在擴(kuò)展里添加了一個(gè)返回值為可選的 Compressed 結(jié)構(gòu)的名為 compressed(with:) 的方法,這個(gè)方法簡(jiǎn)單的調(diào)用了結(jié)構(gòu)體 Compressed 中的靜態(tài)方法 compress(input:with:) 。在這還實(shí)現(xiàn)了使用的例子,但目前是不能正常工作的,我們來(lái)開始完善它。
滾動(dòng)到你寫的第一個(gè)代碼塊,按以下內(nèi)容實(shí)現(xiàn) perform(:on:using:workingBufferSize:)_ 函數(shù):
func perform(_ operation: CompressionOperation,
on input: Data,
using algorithm: CompressionAlgorithm,
workingBufferSize: Int = 2000) -> Data? {
// set the algorithm
let streamAlgorithm: compression_algorithm
switch algorithm {
case .lz4: streamAlgorithm = COMPRESSION_LZ4
case .lz4a: streamAlgorithm = COMPRESSION_LZMA
case .zlib: streamAlgorithm = COMPRESSION_ZLIB
case .lzfse: streamAlgorithm = COMPRESSION_LZFSE
}
// set the stream operation and flags
let streamOperation: compression_stream_operation
let flags: Int32
switch operation {
case .compression:
streamOperation = COMPRESSION_STREAM_ENCODE
flags = Int32(COMPRESSION_STREAM_FINALIZE.rawValue)
case .decompression:
streamOperation = COMPRESSION_STREAM_DECODE
flags = 0
}
return nil /// To be continued
}
這將從 Swift 類型轉(zhuǎn)換為壓縮庫(kù)所需的 C 類型,用于執(zhí)行壓縮算法和操作。
下一步,實(shí)現(xiàn) return nil 部分:
// 1: create a stream
var streamPointer = UnsafeMutablePointer<compression_stream>.allocate(capacity: 1)
defer {
streamPointer.deallocate(capacity: 1)
}
// 2: initialize the stream
var stream = streamPointer.pointee
var status = compression_stream_init(&stream, streamOperation, streamAlgorithm)
guard status != COMPRESSION_STATUS_ERROR else {
return nil
}
defer {
compression_stream_destroy(&stream)
}
// 3: set up a destination buffer
let dstSize = workingBufferSize
let dstPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: dstSize)
defer {
dstPointer.deallocate(capacity: dstSize)
}
return nil /// To be continued
以下就是這里發(fā)生的事情:
- 給 compression_stream 分配內(nèi)存空間,并在 defer 代碼釋放
- 之后,使用 pointee 屬性初始化變量 stream 并將其作為參數(shù)傳遞給函數(shù) compression_stream_init,編譯器在這里做一些特別的事情,使用取地址符號(hào) & 獲取 compression_stream 并將其自動(dòng)轉(zhuǎn)化為 UnsafeMutablePointer<compression_stream>(你也可以直接把 streamPointer 作為參數(shù)傳遞進(jìn)去,不需要做特殊的轉(zhuǎn)換)。(其實(shí)就是通過(guò) compression_stream_init 給 streamPointer 賦值)
- 最后,創(chuàng)建一個(gè)目標(biāo)緩沖區(qū),作為的工作緩沖區(qū)。
替換 return nil 來(lái)結(jié)束 perform 函數(shù):
// process the input
return input.withUnsafeBytes { (srcPointer: UnsafePointer<UInt8>) in
// 1
var output = Data()
// 2
stream.src_ptr = srcPointer
stream.src_size = input.count
stream.dst_ptr = dstPointer
stream.dst_size = dstSize
// 3
while status == COMPRESSION_STATUS_OK {
// process the stream
status = compression_stream_process(&stream, flags)
// collect bytes from the stream and reset
switch status {
case COMPRESSION_STATUS_OK:
// 4
output.append(dstPointer, count: dstSize)
stream.dst_ptr = dstPointer
stream.dst_size = dstSize
case COMPRESSION_STATUS_ERROR:
return nil
case COMPRESSION_STATUS_END:
// 5
output.append(dstPointer, count: stream.dst_ptr - dstPointer)
default:
fatalError()
}
}
return output
}
這就是真正產(chǎn)生作用的地方,下面就是它在做的事情:
- 創(chuàng)建一個(gè) Data 對(duì)象,根據(jù)操作類型將存放輸出的壓縮或解壓縮數(shù)據(jù)。
- 使用你分配的指針及其大小設(shè)置源緩沖區(qū)和目標(biāo)緩沖區(qū)。
- 你要確保調(diào)用的 compression_stream_process 也繼續(xù)返回 COMPRESSION_STATUS_OK。
- 目標(biāo)緩沖區(qū)的數(shù)據(jù)將被復(fù)制給 output 變量,最終將作為該函數(shù)的返回值。
- 當(dāng)最后一個(gè)數(shù)據(jù)包進(jìn)入時(shí),標(biāo)有 COMPRESSION_STATUS_END,只有部分目標(biāo)緩沖區(qū)可能需要復(fù)制。
在示例使用中,你可以看到 10000 個(gè)元素的數(shù)組被壓縮到 153 個(gè)字節(jié), 不是太寒酸。
Unsafe Swift 例子 2:隨機(jī)發(fā)生器
隨機(jī)數(shù)對(duì)許多應(yīng)用程序都是很重要的,從游戲到機(jī)器學(xué)習(xí),macOS 提供了 arc4random 獲取隨機(jī)數(shù),不幸的是,不可在 Linux 上是調(diào)用。此外,arc4random僅僅提供 UInt32 的隨機(jī)數(shù),但是,文件 / dev / urandom 提供了一個(gè)無(wú)限制的隨機(jī)數(shù)來(lái)源。
在本節(jié)中,你將使用新知識(shí)來(lái)讀取此文件并創(chuàng)建類型完全的安全隨機(jī)數(shù)。

首先創(chuàng)建一個(gè)新的 playground,命名為 RandomNumbers, 這次確保選擇的是
macOS 平臺(tái)。
創(chuàng)建完成后,請(qǐng)將默認(rèn)內(nèi)容替換為:
import Foundation
enum RandomSource {
static let file = fopen("/dev/urandom", "r")!
static let queue = DispatchQueue(label: "random")
static func get(count: Int) -> [Int8] {
let capacity = count + 1 // fgets adds null termination
var data = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
defer {
data.deallocate(capacity: capacity)
}
queue.sync {
fgets(data, Int32(capacity), file)
}
return Array(UnsafeMutableBufferPointer(start: data, count: count))
}
}
這個(gè)文件變量被聲明為 static,因此只有一個(gè)存在系統(tǒng)中,當(dāng)進(jìn)程退出時(shí),系統(tǒng)會(huì)自動(dòng)關(guān)閉它。有時(shí)可能需要在多線程中使用隨機(jī)數(shù),你需要使用 GCD 串行隊(duì)列來(lái)保護(hù)訪問(wèn)。(防止線程鎖)
get 函數(shù)是真正起作用的地方, 首先,你創(chuàng)建一些未分配的存儲(chǔ)區(qū)域,它將超出你需要的范圍,因?yàn)?fgets 終將為 0,接著,你從文件夾中獲取數(shù)據(jù),確保這做是在 GCD 隊(duì)列中操作的,最后,將其封裝進(jìn) UnsafeMutableBufferPointer 序列后復(fù)制 data 到標(biāo)準(zhǔn)數(shù)組中。
到目前為止,這只會(huì)(安全地)給你一個(gè) Int8 值的數(shù)組, 現(xiàn)在你要對(duì)他進(jìn)行擴(kuò)展。
將以下代碼添加到你的 playground 末尾處:
extension Integer {
static var randomized: Self {
let numbers = RandomSource.get(count: MemoryLayout<Self>.size)
return numbers.withUnsafeBufferPointer { bufferPointer in
return bufferPointer.baseAddress!.withMemoryRebound(to: Self.self,
capacity: 1) {
return $0.pointee
}
}
}
}
Int8.randomized
UInt8.randomized
Int16.randomized
UInt16.randomized
Int16.randomized
UInt32.randomized
Int64.randomized
UInt64.randomized
在這為 Integer 協(xié)議的所有子類添加了一個(gè)靜態(tài)的 randomized 屬性(了解更多的面向協(xié)議的編程,請(qǐng)看這)。首先你將獲得隨機(jī)數(shù)字,并返回字節(jié)數(shù)組,重新綁定(如 C ++ 的 reinterpret_cast )Int8 值作為請(qǐng)求的類型并返回一個(gè)副本,簡(jiǎn)單吧!??
就是這樣的!使用 unsafe Swift 引擎,是獲取隨機(jī)數(shù)的一種安全的方式。