最近在學習中遇到到 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 框架。
內存布局
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 種指針類型。
在以下部分中,你將了解有關這些指針類型的更多信息。
使用原生的指針
將以下代碼添加到你的 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 指針存儲和讀取了兩個整型數據,接下來說明下它們是什么發生的:
- 這些常量會經常被用到:
- count 表示整型數據存儲的個數
- stride 表示 Int 類型的步幅(即占用的空間跨度)
- alignment 表示 Int 類型的對齊方式
- byteCount 表示總共需要的字節數
- 添加一個 do 代碼塊,控制代碼作用域,因此你可以做接下來的例子中重用變量名
- 方法 UnsafeMutableRawPointer.allocate 是用來分配所需的字節數,它返回的是一個 UnsafeMutableRawPointer 類型,通過名字你就可以知道這個指針是可以讀取和可存儲原生字節數的。(即可變的)
- 添加 defer 代碼塊是確保指針得到正確的釋放,ARC 在這種情況下是幫不了你的,你得自己管理內存,關于 defer 你可以閱讀這篇文章。
- 方法 storeBytes 和 load 分別是用來存儲和讀取字節數的,第二個整數的存儲器地址是通過推進指針步幅字節數來計算的。
由于指針是具有跨域能力的,你也可以通過指針運算來調用 (pointer + stride).storeBytes(of: 6, as: Int.self) 。 - UnsafeRawBufferPointer 讓你訪問內存就像是一個字符集一樣,這意味著你可以迭代字節,可以使用下標訪問它們,甚至使用酷的方法,如過 filter,map 和 reduce 。緩沖區指針使用了原生指針進行初始化。
使用類型指針
前面的例子可以通過使用類型指針進行簡化,將以下代碼加到你的 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 類型的值。
- 類型內存必須在使用前初始化,并在使用完之后銷毀,這一操作分別是由 initialize 和 deinitialize方法來完成。備注 : 正如用戶 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 也可以對 Array 和 Data 實例使用。
校驗和計算
使用 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
}
}
不要一次將內存綁定到兩個不相關的類型上,這叫做類型歧義,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
以下就是這里發生的事情:
- 給 compression_stream 分配內存空間,并在 defer 代碼釋放
- 之后,使用 pointee 屬性初始化變量 stream 并將其作為參數傳遞給函數 compression_stream_init,編譯器在這里做一些特別的事情,使用取地址符號 & 獲取 compression_stream 并將其自動轉化為 UnsafeMutablePointer<compression_stream>(你也可以直接把 streamPointer 作為參數傳遞進去,不需要做特殊的轉換)。(其實就是通過 compression_stream_init 給 streamPointer 賦值)
- 最后,創建一個目標緩沖區,作為的工作緩沖區。
替換 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
}
這就是真正產生作用的地方,下面就是它在做的事情:
- 創建一個 Data 對象,根據操作類型將存放輸出的壓縮或解壓縮數據。
- 使用你分配的指針及其大小設置源緩沖區和目標緩沖區。
- 你要確保調用的 compression_stream_process 也繼續返回 COMPRESSION_STATUS_OK。
- 目標緩沖區的數據將被復制給 output 變量,最終將作為該函數的返回值。
- 當最后一個數據包進入時,標有 COMPRESSION_STATUS_END,只有部分目標緩沖區可能需要復制。
在示例使用中,你可以看到 10000 個元素的數組被壓縮到 153 個字節, 不是太寒酸。
Unsafe Swift 例子 2:隨機發生器
隨機數對許多應用程序都是很重要的,從游戲到機器學習,macOS 提供了 arc4random 獲取隨機數,不幸的是,不可在 Linux 上是調用。此外,arc4random僅僅提供 UInt32 的隨機數,但是,文件 / dev / urandom 提供了一個無限制的隨機數來源。
在本節中,你將使用新知識來讀取此文件并創建類型完全的安全隨機數。
首先創建一個新的 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 引擎,是獲取隨機數的一種安全的方式。