不安全 Swift: 使用指針與 C 進(jìn)行交互(譯)

最近在學(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)存布局

memory-480x214.png

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 種指針類型。


pointers-650x444.png

在以下部分中,你將了解有關(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ā)生的:

  1. 這些常量會(huì)經(jīng)常被用到:
  • count 表示整型數(shù)據(jù)存儲(chǔ)的個(gè)數(shù)
  • stride 表示 Int 類型的步幅(即占用的空間跨度)
  • alignment 表示 Int 類型的對(duì)齊方式
  • byteCount 表示總共需要的字節(jié)數(shù)
  1. 添加一個(gè) do 代碼塊,控制代碼作用域,因此你可以做接下來(lái)的例子中重用變量名
  2. 方法 UnsafeMutableRawPointer.allocate 是用來(lái)分配所需的字節(jié)數(shù),它返回的是一個(gè) UnsafeMutableRawPointer 類型,通過(guò)名字你就可以知道這個(gè)指針是可以讀取和可存儲(chǔ)原生字節(jié)數(shù)的。(即可變的)
  3. 添加 defer 代碼塊是確保指針得到正確的釋放,ARC 在這種情況下是幫不了你的,你得自己管理內(nèi)存,關(guān)于 defer 你可以閱讀這篇文章
  4. 方法 storeBytesload 分別是用來(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) 。
  5. UnsafeRawBufferPointer 讓你訪問(wèn)內(nèi)存就像是一個(gè)字符集一樣,這意味著你可以迭代字節(jié),可以使用下標(biāo)訪問(wèn)它們,甚至使用酷的方法,如過(guò) filter,mapreduce 。緩沖區(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)存必須在使用前初始化,并在使用完之后銷毀,這一操作分別是由 initializedeinitialize方法來(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ì) ArrayData 實(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
   }
}
badpun-480x175.png

不要一次將內(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ā)生的事情:

  1. compression_stream 分配內(nèi)存空間,并在 defer 代碼釋放
  2. 之后,使用 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_initstreamPointer 賦值)
  3. 最后,創(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)生作用的地方,下面就是它在做的事情:

  1. 創(chuàng)建一個(gè) Data 對(duì)象,根據(jù)操作類型將存放輸出的壓縮或解壓縮數(shù)據(jù)。
  2. 使用你分配的指針及其大小設(shè)置源緩沖區(qū)和目標(biāo)緩沖區(qū)。
  3. 你要確保調(diào)用的 compression_stream_process 也繼續(xù)返回 COMPRESSION_STATUS_OK。
  4. 目標(biāo)緩沖區(qū)的數(shù)據(jù)將被復(fù)制給 output 變量,最終將作為該函數(shù)的返回值。
  5. 當(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ù)。

hexdump-480x202.png

首先創(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ù)的一種安全的方式。

參考文獻(xiàn)

最后編輯于
?著作權(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)容