默認狀況下,Swift是內存安全的,這就意味着 Swift 會避免內存的直接訪問,並肯定全部變量會在初始化後才進行使用。這裏的關鍵詞語是「默認的」。也就是說,當咱們須要時,不安全的 Swift 仍是能夠經過指針來直接訪問到內存地址的swift
這篇教程會告訴你爲啥 Swift 又是「不安全的」。「不安全」這個詞可能會令人誤解,它不是說咱們寫的代碼可能會發生出乎意料的問題,而是說咱們須要額外注意這些代碼,由於編譯器在編譯過程當中並不會給予咱們幫助,咱們沒法直觀地捕獲到錯誤。緩存
在開發工做中,當你須要與C這些不安全的語言進行交互時,就會用得上這篇文章所介紹的特性了。安全
這篇教程由3個 playgrounds 組成。在第一個 playground 中,將會建立幾個簡短的片斷來了解內存佈局而且使用不安全的指針。在第二個 playground 中,會封裝C語言的API來執行流數據的壓縮。最後的 playground ,建立arc4random
的替代函數dom
首先建立一個新的 playground,名稱爲UnsafeSwift
ide
不安全的 Swift 會直接在內存系統中進行交互。內存能夠經過一系列格子來進行可視化,每一個格子裏是與內存相關的惟一性的內存地址。存儲的最小單位是 byte,由8個 bits 組成。8 bit 的 byte 能夠保存0-255大小的數字。函數
Swift 有一個MemoryLayout
的類能夠告訴咱們每一個類型對象的大小和分佈。添加以下代碼:佈局
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>
會在編譯時肯定指定類型的size
,alignment
和stride
。舉個例子來講, Int16
的size
是2個byte,內存對齊也是2.這就意味着其內存地址必須是偶數地址ui
所以,假設地址100和101給Int16
分配地址的話,確定就是選擇100了, 由於101違背了對齊原則。當咱們將一堆Int16
打包在一塊兒的話,stride
表示的是,當前類型的內存地址開頭與下一個內存地址開頭之間的距離spa
接下來,看看用戶自定義的structs
的內存佈局:設計
MemoryLayout<EmptyStruct>.size // 0
MemoryLayout<EmptyStruct>.alignment // 1
MemoryLayout<EmptyStruct>.stride // 1
struct SampleStruct {
let number: UInt32
let flag: Bool
}
MemoryLayout<SampleStruct>.size // 5
MemoryLayout<SampleStruct>.alignment // 4
MemoryLayout<SampleStruct>.stride // 8
複製代碼
空的結構體的size爲0.由於空結構體的stride
爲1,因此它能夠分配在任意的地址上。
對於SampleStruct來講, 其size
爲5,stride
爲8.這是由於內存對齊的位數是4個字節。
而後看下類對象的:
class EmptyClass {}
MemoryLayout<EmptyClass>.size // 8
MemoryLayout<EmptyClass>.alignment // 8
MemoryLayout<EmptyClass>.stride // 8
class SampleClass {
let number: Int64 = 0
let flag: Bool = false
}
MemoryLayout<SampleClass>.size // 8
MemoryLayout<SampleClass>.alignment // 8
MemoryLayout<SampleClass>.stride // 8
複製代碼
能夠看到,類對象的size
,alignment
和stride
都是8,且不論是否空的對象。
指針對象包含了一個內存地址。直接涉及內存訪問的類型會有一個unsafe
的前綴,因此其指針稱爲UnsafePointer
. 雖然長長的類型看起來會比較煩,可是可使咱們清楚地知道相關的代碼是沒有通過相關的編譯器檢查,可能會致使未定義的行爲(而不只僅是一個可預見的崩潰)。
Swift 的設計者其實能夠建立了一個UnsafePointer
類型,而且該類型與C語言中的char *
相等,能夠用來以非結構化方式來訪問內存。可是他們並無。Swift 涵蓋了大部分的指針類型,每種類型都有不一樣的用處和目的。使用合適的指針類型能夠更好地達到咱們的需求,更少地引發錯誤。
不安全的 Swift 指針的命名可讓咱們知道該指針的特徵。可變( Mutable )或者不可變( Immutable ), 原始的( raw ) 或者其餘類型的( typed ),是不是緩存風格( buffer style ). 在 Swift 中,一共有8種類型組合:
Unsafe[Mutable][Raw][Buffer]Pointer[]
指針就是內存地址,直接訪問內存就是 Unsafe 的
Mutable 表示可寫
Raw 表示它是否指向了二進制數據類型的字節(blob of bytes)
Buffer 表示其是不是一個結合
// 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)")
}
}
// Output
// Raw pointers
// byte 0: 42
// byte 1: 0
// byte 2: 0
// byte 3: 0
// byte 4: 0
// byte 5: 0
// byte 6: 0
// byte 7: 0
// byte 8: 6
// byte 9: 0
// byte 10: 0
// byte 11: 0
// byte 12: 0
// byte 13: 0
// byte 14: 0
// byte 15: 0
複製代碼
上面的例子中,使用不安全的 Swift 指針來保存並加載兩個整數:
常量:
count
: 要保存的整數的個數stride
: Int
的步長alignment
: Int
的內存對齊位數byteCount
: 總字節數do
添加了一個塊級做用域,方便從新使用變量名
UnsafeMutableRawPointer.allocate
用於分配所須要的字節數。該方法返回一個UnsafeMutableRawPointer
指針。從名稱咱們能夠得知該指針能夠用來加載和保存原始類型的字節
defer
用來保證指針可以獲得釋放。
storeBytes
和load
用於存儲和加載字節。第二個整數的內存地址根據指針的步長進行計算得出。
UnsafeRawBufferPointer
讓咱們能夠像訪問字節集合同樣來對內存地址進行訪問。就是咱們能夠遍歷字節,經過下標訪問,或者是調用map
, filter
等方法。緩存區的指針須要使用原始指針來進行初始化
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.advanced(by: 1).pointee = 6
pointer.pointee
pointer.advanced(by: 1).pointee
let bufferPointer = UnsafeBufferPointer(start: pointer, count: count)
for (index, value) in bufferPointer.enumerated() {
print("value \(index): \(value)")
}
}
// Output
// Typed pointers
// value 0: 42
// value 1: 6
複製代碼
與原始指針的不一樣點在於:
UnsafeMutablePointer.allocate
方法用於分配內存。pointee
用於加載和保存值類型指針並不必定須要直接進行初始化,也能夠經過原始指針來進行轉換:
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)")
}
}
// Output
// Converting raw pointers to typed pointers
// value 0: 42
// value 1: 6
複製代碼
這個例子除了首先建立了原始指針而後將內存綁定到類型指針上之外,與上一個類似。綁定內存後,咱們就能經過一種類型安全的方式對內存進行訪問。內存綁定在咱們建立類型指針時會隱式進行
通常狀況下,咱們能夠經過withUnsafeBytes(of:)
方法來獲取一個實例對象的字節。
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)
}
}
}
// Output
// Getting the bytes of an instance
// 25
// 0
// 0
// 0
// 1
複製代碼
這個例子輸出了SampleStruct
的實例的原始字節,withUnsafeBytes(of:)
方法容許咱們對UnsafeRawBufferPointer
進行訪問。
withUnsafeBytes
也能夠用於Array
和Data
的實例。
可使用withUnsafeBytes(of:)
來返回一個結果。下面的例子用來計算結構體中的32位校驗和
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)
}
複製代碼
withUnsafeBytes
中返回指針