默认状况下,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
中返回指针