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

最近在學習中遇到到 Swift 和 C 第三方庫交互的問題,Google 到了這篇文章順便記錄了下,文章原文出處《unsafe Swift: Using Pointers And Interacting With C》

默認情況下,Swift 是內存安全的,這意味著避免直接訪問內存,并且在使用前要確保一切都是初始化過的。當然這是在默認情況下,在我們需要時不安全(unsafe)Swift (以下內容將直接用 unsafe Swift,翻譯了感覺別扭)允許我們通過指針直接訪問內存。

這篇教程將帶你快速領略所謂“unsafe"的 Swift 特征。術語“unsafe”有時會讓人感到迷惑,它并不是說你寫的代碼是危險的、不可運行的爛代碼,僅僅意味著你在寫代碼時需要格外的謹慎,因為編譯器在這方面對你的幫助是有限的。

當你要與類似 C 這樣的不安全語言進行交互時就會用到這些特征,比如要?提高運行時性能或探究 Swift 的內部原理。雖然這是一個高級課程,但如果你有正常的 Swift 能力,你應該能夠跟得上課程。 C 經驗將對你的學習有所幫助,但并不是必需的。

入門

這一課程由三個 playgrounds 組成,在第一個 playground,你將創建幾個短片段來探索內存布局并使用不安全的指針,在第二個 playground,你將使用 Swift 接口封裝一個低階的 C API 來展示數據流壓縮,最后一個 playground,您將創建一個平臺獨立的替代 arc4random,在使用不安全的 Swift 時,隱藏用戶的細節。

開始創建一個新的 playground,命名為 UnsafeSwift, 您可以選擇任何平臺,本教程中的所有代碼都與平臺無關, 確保導入 Foundation 框架。

內存布局

memory-480x214.png

unsafe Swift 直接與內存系統配合使用,內存可以被視為一系列的盒子(實際上是數十億個盒子),每個盒子里面都有一個數字,每一個盒子都有一個唯一的與之關聯的內存地址,最小的尋址儲存單元是一個字節,一個字節由 8 位(bits) 組成,8 位字節可以存儲 0-255 的值。處理器通常也可以有效地訪問多于一個字節的存儲器的字。 例如,在 64 位系統上,一個字是 8 字節或 64 位長度。

Swift 有一個 MemoryLayout 技巧,可以告訴你程序中的類型大小和對齊方式。

將以下代碼添加到你的 playground:

MemoryLayout<Int>.size          // returns 8 字節 (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> 是在編譯時評估的通用類型,用于確定每個指定類型的大小,對齊方式和步幅,返回的數字是對應數據類型的字節數。 例如,Int16 的大小是兩個字節,并且也有兩個對齊方式,這意味著它必須從偶數地址開始(可以被 2 整除)。

因此,例如,給一個 Int16 數據類型分配的地址是 100,那就是合法的,但如果分配的是 101 就不合法了,因為它違反了對齊方式的要求。當你將一堆的 Int16 類型數據放在一起,他們將以一定的間隔(步幅)打包在一起,對于這些基本類型,大小與間隔(步幅)都是相同。

接下來,查看一些自定義的結構體的布局,并將以下內容添加到 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

空結構體的大小為 0,它可以放在任何地址,因為它對齊方式是 1(即所有數字都可以被 1 整除)。奇怪的是步幅也是 1,這是因為你創建的每個 EmptyStruct 必須具有唯一的內存地址,盡管大小為零。

對于 SampleStruct,大小是 5,但間隔(步幅)卻是 8,這是由其對齊方式以 4 個字節為邊界決定的,考慮到這,最好情況下 Swift 能做到以 8 個字節間隔打包。

接下來添加:

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 個字節,如果你想了解更詳細的內存布局,請看this excellent talk by Mike Ash

指針

指針指向(封裝)的是一個內存地址,那些涉及到直接訪問內存地址的類型都會有“unsafe”前綴,因此,指針類型也被稱作 UnsafePointer。雖然額外的輸入可能看起來很煩人,但它讓你和你的讀者知道你正在將內存中的非編譯器檢查的訪問權限浸入到未正確使用的操作中,從而導致不可預測的行為(而不僅僅是可預測的崩潰)。

Swift 的設計師可以僅僅創建單一 UnsafePointer 類型,并將其作為 char * 的 C 等價物,可以以非結構化的方式訪問內存,但他們沒有這樣做,相反,Swift 包含幾十種指針類型,每種類型具有不同的功能和目的。使用適當的指針類型可以防止錯誤的發生并且更清晰地表達開發者的意圖,讓你控制不可預測行為的產生。

Unsafe Swift 指針使用可預測的命名方案,以便你知道指針的特征是什么。可變或者不可變,原生(raw)或者有類型的,是否是緩沖(buffer)類型,這三種特性總共組合出了 8 種指針類型。


pointers-650x444.png

在以下部分中,你將了解有關這些指針類型的更多信息。

使用原生的指針

將以下代碼添加到你的 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)")
   }
}

在這個例子里你使用 Unsafe Swift 指針存儲和讀取了兩個整型數據,接下來說明下它們是什么發生的:

  1. 這些常量會經常被用到:
  • count 表示整型數據存儲的個數
  • stride 表示 Int 類型的步幅(即占用的空間跨度)
  • alignment 表示 Int 類型的對齊方式
  • byteCount 表示總共需要的字節數
  1. 添加一個 do 代碼塊,控制代碼作用域,因此你可以做接下來的例子中重用變量名
  2. 方法 UnsafeMutableRawPointer.allocate 是用來分配所需的字節數,它返回的是一個 UnsafeMutableRawPointer 類型,通過名字你就可以知道這個指針是可以讀取和可存儲原生字節數的。(即可變的)
  3. 添加 defer 代碼塊是確保指針得到正確的釋放,ARC 在這種情況下是幫不了你的,你得自己管理內存,關于 defer 你可以閱讀這篇文章。
  4. 方法 storeBytesload 分別是用來存儲和讀取字節數的,第二個整數的存儲器地址是通過推進指針步幅字節數來計算的。
    由于指針是具有跨域能力的,你也可以通過指針運算來調用 (pointer + stride).storeBytes(of: 6, as: Int.self)
  5. UnsafeRawBufferPointer 讓你訪問內存就像是一個字符集一樣,這意味著你可以迭代字節,可以使用下標訪問它們,甚至使用酷的方法,如過 filter,mapreduce 。緩沖區指針使用了原生指針進行初始化。

使用類型指針

前面的例子可以通過使用類型指針進行簡化,將以下代碼加到你的 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 指向的內存地址存放數值 42
   pointer.advanced(by: 1).pointee = 6 // pointer 下一個內存地址存放數值 6,即 pointer 指向的起始地址加 Int 類型的步幅再移動 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)")
   }
}

注意以下不同點:

  • 使用 UnsafeMutablePointer.allocate 分配內存,泛型參數(即<T>中的類型)讓 Swift 知道指針將用于加載和存儲 Int 類型的值。
  • 類型內存必須在使用前初始化,并在使用完之后銷毀,這一操作分別是由 initializedeinitialize方法來完成。備注 : 正如用戶 atrick 在下面的評論中所指出的,初始化只適用于非基礎數據類型。也就是說,包含銷毀過程對于你將來校對代碼是一種好的方式。通常情況下它是不會有什么不好的結果出現的,因為編譯器會對它進行優化。
  • 類型指針有一個 pointee 屬性提供了類型安全的方式來讀取和存儲值。
  • 當需要指針前進的時候,我們只需要指定想要前進的個數,類型指針會自動根據它所指向的數值類型來計算要間隔的值。同樣的,我們可以直接對指針進行算術運算 (pointer + 1).pointee = 6 。
  • 有類型的緩沖型指針也會直接操作數值,而非字節。

轉換原生指針到類型指針

類型指針并不總是通過直接初始化得到,它們也可以從原生指針派生而來。

添加以下代碼到你的 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)")
    }
}

這個例子和上一個例子是非常的相似的,除了剛開始創建的原生指針之外,例子中的類型指針是通過綁定 Int 類型所需的內存來創建的,這種內存綁定是類型安全的,當你創建一個類型化的指針時,內存綁定是在幕后完成的。
本示例的其余部分與前一個相同, 一旦你在類型化的指針土地,你可以像例子那樣使用pointee

獲取實例的字節數

你常常會遇到對已存在某數據類型實例做字節數檢查,這時你可以通過調用 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)
     }
   }
}

這將會打印出 SampleStruct 實例的原生字節數,withUnsafeBytes(of:) 方法可以訪問到 UnsafeRawBufferPointer 并傳入閉包中供你使用。

withUnsafeBytes 也可以對 ArrayData 實例使用。

校驗和計算

使用 withUnsafeBytes(of:) 你可以得到一個返回值,以下例子使用它來計算 32 位操作系統結構體字節的校驗和。( checksum,使用少量的數據來驗證數據在傳輸或者存儲時是否存在錯誤;通常情況下,是用來檢查數據的完整性,但是不保證數據的準確性可靠性)

將以下代碼添加到你的 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
}

通過 reduce 調用將所有字節相加,然后使用 ~ 按位取反,這不是一個特別強大的錯誤檢測,但它體現了這個概念。

Unsafe 俱樂部的三大原則

你在寫不安全代碼時需要格外小心,以避免不可預知的行為,這里有一些爛代碼的例子。

不要讓 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 !!!
}

你不應該在 withUnsafeBytes(of:) 閉包中出現逃逸指針,這一刻可能能正常執行,但······(下一刻可能就執行不了了)

一次只綁定一種類型

// 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

不要一次將內存綁定到兩個不相關的類型上,這叫做類型歧義,Swift 不支持這樣的歧義。然而,你可以使用 withMemoryRebound(to:capacity:) 方法臨時重新綁定內存。另外,這一規則表示從一個基本數據類型(例如 __Int __)重新綁定到一個非基本數據類型(例如 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
   }
} 

隨著不安全代碼使用,錯誤的問題會一個接一個呈現出來,這太糟糕,所以使用時要謹慎小心,要進行審查和測試。

Unsafe Swift 例子1:壓縮

使用你所掌握的知識封裝 C API,在 Cocoa 中引用 C 模塊實現一些通用的數據壓縮算法,這些包括速度至關重要的 LZ4,當你需要最高的壓縮比并且不關心速度時的 LZ4A,能平衡空間和時間的 ZLIB,還有能夠更好的平衡空間和時間的新庫(開源庫)LZFSE。

創建一個新的 playground,命名為 Compression,首先定義一個使用 Data 的純 Swift API。

接下來,使用以下代碼來替換你 playground 中的內容:

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
}

函數 perform 實現的是壓縮和解壓,當前只設置返回 nil,很快你就會給它添加些 unsafe 代碼。
接下來在 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)
   } 
}

結構體 Compressed 存儲了要壓縮的數據和將要使用到的壓縮算法,這使得你在使用解壓縮算法時減少出錯的可能。
接著在 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

這里主要的入口點是 Data 數據類型的擴展,你已在擴展里添加了一個返回值為可選的 Compressed 結構的名為 compressed(with:) 的方法,這個方法簡單的調用了結構體 Compressed 中的靜態方法 compress(input:with:) 。在這還實現了使用的例子,但目前是不能正常工作的,我們來開始完善它。
滾動到你寫的第一個代碼塊,按以下內容實現 perform(:on:using:workingBufferSize:)_ 函數:

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 類型轉換為壓縮庫所需的 C 類型,用于執行壓縮算法和操作。
下一步,實現 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

以下就是這里發生的事情:

  1. compression_stream 分配內存空間,并在 defer 代碼釋放
  2. 之后,使用 pointee 屬性初始化變量 stream 并將其作為參數傳遞給函數 compression_stream_init,編譯器在這里做一些特別的事情,使用取地址符號 & 獲取 compression_stream 并將其自動轉化為 UnsafeMutablePointer<compression_stream>(你也可以直接把 streamPointer 作為參數傳遞進去,不需要做特殊的轉換)。(其實就是通過 compression_stream_initstreamPointer 賦值)
  3. 最后,創建一個目標緩沖區,作為的工作緩沖區。

替換 return nil 來結束 perform 函數:

// 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
}

這就是真正產生作用的地方,下面就是它在做的事情:

  1. 創建一個 Data 對象,根據操作類型將存放輸出的壓縮或解壓縮數據。
  2. 使用你分配的指針及其大小設置源緩沖區和目標緩沖區。
  3. 你要確保調用的 compression_stream_process 也繼續返回 COMPRESSION_STATUS_OK。
  4. 目標緩沖區的數據將被復制給 output 變量,最終將作為該函數的返回值。
  5. 當最后一個數據包進入時,標有 COMPRESSION_STATUS_END,只有部分目標緩沖區可能需要復制。

在示例使用中,你可以看到 10000 個元素的數組被壓縮到 153 個字節, 不是太寒酸。

Unsafe Swift 例子 2:隨機發生器

隨機數對許多應用程序都是很重要的,從游戲到機器學習,macOS 提供了 arc4random 獲取隨機數,不幸的是,不可在 Linux 上是調用。此外,arc4random僅僅提供 UInt32 的隨機數,但是,文件 / dev / urandom 提供了一個無限制的隨機數來源。
在本節中,你將使用新知識來讀取此文件并創建類型完全的安全隨機數。

hexdump-480x202.png

首先創建一個新的 playground,命名為 RandomNumbers, 這次確保選擇的是
macOS 平臺。
創建完成后,請將默認內容替換為:

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))
   }
}

這個文件變量被聲明為 static,因此只有一個存在系統中,當進程退出時,系統會自動關閉它。有時可能需要在多線程中使用隨機數,你需要使用 GCD 串行隊列來保護訪問。(防止線程鎖)
get 函數是真正起作用的地方, 首先,你創建一些未分配的存儲區域,它將超出你需要的范圍,因為 fgets 終將為 0,接著,你從文件夾中獲取數據,確保這做是在 GCD 隊列中操作的,最后,將其封裝進 UnsafeMutableBufferPointer 序列后復制 data 到標準數組中。

到目前為止,這只會(安全地)給你一個 Int8 值的數組, 現在你要對他進行擴展。

將以下代碼添加到你的 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 協議的所有子類添加了一個靜態的 randomized 屬性(了解更多的面向協議的編程,請看這)。首先你將獲得隨機數字,并返回字節數組,重新綁定(如 C ++ 的 reinterpret_cast )Int8 值作為請求的類型并返回一個副本,簡單吧!??

就是這樣的!使用 unsafe Swift 引擎,是獲取隨機數的一種安全的方式。

參考文獻

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,347評論 2 377

推薦閱讀更多精彩內容