此篇文章背景源自一次偶现高频次崩溃问题排查。底层长链接通讯采用 Rust 编写,涉及与业务层的桥接:Rust <-> C <-> Swift
,虽然说 Rust 与 Swift 都以安全著称,但不论是 Rust FFI 到 C 仍是 Swift 与 C 的交互,代码中都不得不触及unsafe
关键字,也是此次崩溃问题的缘由所在,这就不难理解为何 Swift 和 Rust 的设计者绝不保留地采用Unsafe-
命名与unsafe
关键字了。编程

// typedef struct {
// const uint8_t *array;
// size_t len;
// } ByteArray;
// ......
// 问题代码
let bodyToSend = bodyData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) in
bytes
}
// 修复代码
let bodyToSend = [UInt8](bodyData)
// 调用 C 方法与 Rust 交互
let len = bodyData.count
let bodyByteArray = ByteArray(array: bodyToSend, len: len)
halo_send_message(1, namespace, path, metadataToSend, bodyByteArray)
// ......
复制代码
withUnsafe-
方法中获取的指针必定不要让其"逃出"「不安全区」,仅在所属不安全闭包中使用,不然该指针将再也不受控制,致使不可预测的问题,如崩溃。let bodyToSend = UnsafePointer(&bodyData) // 必定不要这么获取变量指针
复制代码
想必稍微对 Swift 语言有所了解都会知道这是一门安全的编程语言,所以,在谈及其不安全的部分以前,先说说它的安全性:swift
关于安全性,Swift 语言的设计者们对此的定义不是不崩溃,而是:数组
永远不会在无心中访问错误的内存安全
为此,Swift 作两件事,一是让编译器时刻提醒开发者注意安全;二则是开发者强行开车致使产生未定义的行为的话,当即原地爆炸💥,避免更严重的问题发生。闭包
既然 Swift 追求安全,为何要设计不安全的部分呢?通俗地讲,就是容许有经验的老司机开黑车:防抱死功能一关,请开始你的表演。编程语言
认识UnsafePoint
前,咱们先来了解下 Swift 如何对内存进行分配与布局的。ide
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<String>.size // returns 16
MemoryLayout<String>.alignment // returns 8
MemoryLayout<String>.stride // returns 16
let zero = 0.0
MemoryLayout.size(ofValue: zero) // return 8, zero as Double implictly
struct EmptyStruct {}
MemoryLayout<EmptyStruct>.size // returns 0
MemoryLayout<EmptyStruct>.alignment // returns 1
MemoryLayout<EmptyStruct>.stride // returns 1
struct SampleStruct {
var number: UInt32
var flag: Bool // {
// didSet {
// print("wow")
// }
// }
}
MemoryLayout<SampleStruct>.size // returns 5
MemoryLayout<SampleStruct>.alignment // returns 4
MemoryLayout<SampleStruct>.stride // returns 8
MemoryLayout.offset(of: \SampleStruct.flag) // return 4 without didSet; return nil with didSet
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.offset(of: \SampleClass.flag) // return nil
复制代码
为了解 Swift 中的布局状况,咱们用到了自带的MemoryLayout
工具类。工具
MemoryLayout<T>.size
:一个 T 类型数据实例所占的连续内存大小,单位:bytesMemoryLayout<T>.alignment
:数据类型 T 数据类型的对齐原则大小,单位:bytesMemoryLayout<T>.stride
:一个 T 类型数组中,任意一个元素从开始地址到下一个元素的开始地址所占用的连续内存大小,单位:bytes在 Swift 中指针是几种以Unsafe-
前缀与-Pinter
后缀命名的结构体,看得出来,尽管是非安全的指针操做API,Swift 也但愿能尽量地作到安全。布局
UnsafePointer<T>
: 对应于 const T *
UnsafeMutablePointer<T>
:对应于 T *
UnsafeRawPointer
: 对应于 const void *
UnsafeMutableRawPointer
:对应于 void *
let stride = MemoryLayout<Int>.stride
let alignment = MemoryLayout<Int>.alignment
let count = 2
let byteCount = stride * count
let pointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment) // 原始指针对类型无感知,故指定 byteCount 与 alignment,经过 MemoryLayout 得到
defer {
pointer.deallocate()
}
// 原始指针操做均须额外指定类型
pointer.storeBytes(of: 0b111111111111, as: Int.self)
pointer.advanced(by: stride).storeBytes(of: 6, as: Int.self)
(pointer+stride).storeBytes(of: 6, as: Int.self)
pointer.load(as: Int.self)
pointer.advanced(by: stride).load(as: Int.self)
(pointer+stride).load(as: Int.self)
let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount)
for (offset, byte) in bufferPointer.enumerated() {
print("byte \(offset): \(byte)")
}
复制代码
let count = 2
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
pointer.initialize(repeating: 0, count: count) // 只需初始值与指定类型实例数量,相似泛型数组的初始化
defer {
pointer.deinitialize(count: count)
pointer.deallocate()
}
// 操做无须额外指定类型,经过泛型推断
pointer.pointee = 0b111111111111
pointer.advanced(by: 1).pointee = 6
(pointer+1).pointee = 6
pointer.pointee
pointer.advanced(by: 1).pointee
(pointer+1).pointee
let bufferPointer = UnsafeBufferPointer(start: pointer, count: count)
for (offset, value) in bufferPointer.enumerated() {
print("value \(offset): \(value)")
}
复制代码
let count = 2
let stride = MemoryLayout<Int>.stride
let alignment = MemoryLayout<Int>.alignment
let byteCount = stride * count
// Converting raw pointers to typed pointers
let rawPointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)
defer {
rawPointer.deallocate()
}
// 将原始指针转换为泛型指针,同一地址空间仅可 bindMemory 一次
let typedPointer = rawPointer.bindMemory(to: Int.self, capacity: count)
typedPointer.initialize(repeating: 0, count: count)
defer {
typedPointer.deinitialize(count: count)
}
typedPointer.pointee = 0b111111111111
typedPointer.advanced(by: 1).pointee = 6
typedPointer.pointee
typedPointer.advanced(by: 1).pointee
let bufferPointer = UnsafeBufferPointer(start: typedPointer, count: count)
for (offset, value) in bufferPointer.enumerated() {
print("value \(offset): \(value)")
}
复制代码
Swift 中经过 let
和 var
关键字区分变量的可变性,指针中也采用了相似方案,让开发者针对性地控制指针的可变性,即其指向的内存块的可写性。性能
Buffer 指针,其本质就是一个指针+一个大小count
,即一串连续的内存块。
Swift 是一门类型安全的语言,但当代码中出现Unsafe
字样时,务必遵循如下一些指针操做原则,以免未定义行为发生,不然遇到问题时将很是难以定位。
withUnsafe-
方法中返回获取的指针bindMemory
一次仅可绑定一种类型