本文系學(xué)習(xí)Swift中的指針操作詳解的整理
默認(rèn)情況下Swift是內(nèi)存安全的,蘋果官方不鼓勵(lì)我們直接操作內(nèi)存况既。但是罪针,Swift中也提供了使用指針操作內(nèi)存的方法啤它,直接操作內(nèi)存是很危險(xiǎn)的行為普办,很容易就出現(xiàn)錯(cuò)誤工扎,因此官方將直接操作內(nèi)存稱為 “unsafe 特性”。
在操作指針之前衔蹲,需要理解幾個(gè)概念:size肢娘、alignment、stride踪危,以及他們的獲取/使用蔬浙,這就用到了 MemoryLayout :
MemoryLayout
使用MemoryLayout,可以檢測(cè)某個(gè)類型的實(shí)際大姓暝丁(size)畴博,內(nèi)存對(duì)齊大小(alignment)蓝仲,以及實(shí)際占用的內(nèi)存大芯悴 (步長:stride),其單位均為字節(jié)袱结;
public enum MemoryLayout<T> {
public static var size: Int { get }
public static var stride: Int { get }
public static var alignment: Int { get }
public static func size(ofValue value: T) -> Int
public static func stride(ofValue value: T) -> Int
public static func alignment(ofValue value: T) -> Int
}
例如:如果一個(gè)類型的大辛料丁(size)為5字節(jié),對(duì)齊內(nèi)存(alignment)大小為4字節(jié)垢夹,那么其實(shí)際占用的內(nèi)存大幸缥恰(stride)為8字節(jié)果元,這是因?yàn)榫幾g需要為其填充空白的邊界促王,使其符合它的 4 字節(jié)內(nèi)存邊界對(duì)齊倡怎。
常見基本類型的內(nèi)存size迅耘、alignment、stride:
MemoryLayout<Int>.size // return 8 (on 64-bit)
MemoryLayout<Int>.alignment // return 8 (on 64-bit)
MemoryLayout<Int>.stride // return 8 (on 64-bit)
MemoryLayout<Int16>.size // return 2
MemoryLayout<Int16>.alignment // return 2
MemoryLayout<Int16>.stride // return 2
MemoryLayout<Bool>.size // return 1
MemoryLayout<Bool>.alignment // return 1
MemoryLayout<Bool>.stride // return 1
MemoryLayout<Float>.size // return 4
MemoryLayout<Float>.alignment // return 4
MemoryLayout<Float>.stride // return 4
MemoryLayout<Double>.size // return 8
MemoryLayout<Double>.alignment // return 8
MemoryLayout<Double>.stride // return 8
原文中Bool的相關(guān)值為2监署,在Playground中測(cè)試結(jié)果為1颤专,所以這里更改為了1
一般在移動(dòng)指針的時(shí)候,對(duì)于特定類型钠乏,指針一次移動(dòng)一個(gè)stride(步長)血公,移動(dòng)的范圍,要在分配的內(nèi)存范圍內(nèi)缓熟,切記超出分配的內(nèi)存空間累魔,切記超出分配的內(nèi)存空間,切記超出分配的內(nèi)存空間够滑。
一般情況下stride是alignment的整數(shù)倍垦写,即符合內(nèi)存對(duì)齊原則;實(shí)際分配的內(nèi)存空間大小也是alignment的整數(shù)倍彰触,但是實(shí)際實(shí)例大小可能會(huì)小于實(shí)際分配的內(nèi)存空間大小梯投。
UnsafePointer
所有指針類型為 UnsafePointer,一旦你操作了內(nèi)存况毅,編譯器不會(huì)對(duì)這種操作進(jìn)行檢測(cè)分蓖,你需要對(duì)自己的代碼承擔(dān)全部的責(zé)任。Swift中定義了一些特定類型的指針尔许,每個(gè)類型都有他們的作用和目的么鹤,使用適當(dāng)?shù)闹羔橆愋涂梢苑乐瑰e(cuò)誤的發(fā)生,并且更清晰得表達(dá)開發(fā)者的意圖味廊,防止未定義行為的產(chǎn)生蒸甜。
通過指針類型的名稱,我們可以知道這是一個(gè)什么類型的指針:可變/不可變余佛、原生(raw)/有類型柠新、是否是緩沖類型(buffer),大致有以下8種類型:
Pointer Name | Unsafe辉巡? | Write Access恨憎? | Collection | Strideable? | Typed郊楣? |
UnsafeMutablePointer<T> | yes | yes | no | yes | yes |
UnsafePointer<T> | yes | no | no | yes | yes |
UnsafeMutableBufferPointer<T> | yes | yes | yes | no | yes |
UnsafeBufferPointer<T> | yes | no | yes | no | yes |
UnsafeRawPointer | yes | no | no | yes | no |
UnsafeMutableRawPointer | yes | yes | no | yes | no |
UnsafeMutableRawBufferPointer | yes | yes | yes | no | no |
UnsafeRawBufferPointer | yes | no | yes | no | no |
- unsafe:不安全的
- Write Access:可寫入
- Collection:像一個(gè)容器憔恳,可添加數(shù)據(jù)
- Strideable:指針可使用 advanced 函數(shù)移動(dòng)
- Typed:是否需要指定類型(范型)
原生(Raw)指針
// 1
let count = 2
let stride = MemoryLayout<Int>.stride
let aligment = MemoryLayout<Int>.alignment
let byteCount = stride * count
// 2
do {
print("raw pointers")
// 3
let pointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: aligment)
// 4
defer {
pointer.deallocate()
}
// 5
pointer.storeBytes(of: 42, as: Int.self)
pointer.advanced(by: stride).storeBytes(of: 6, as: Int.self)
// 讀取第一個(gè)值
pointer.load(as: Int.self)
// 讀取第二個(gè)值
pointer.advanced(by: stride).load(as: Int.self)
// 6
let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount)
for (index, byte) in bufferPointer.enumerated() {
print("bute \(index): \(byte)")
}
}
代碼說明:
- 聲明示例用到的參數(shù)
count :整數(shù)的個(gè)數(shù)
stride:整數(shù)的步長
aligment:整數(shù)的內(nèi)存對(duì)齊大小
byteCount:實(shí)際需要的內(nèi)存大小
- 聲明示例用到的參數(shù)
- 聲明作用域
使用 do 來增加一個(gè)作用域,讓我們可以在接下的示例中復(fù)用作用域中的變量名痢甘。
- 聲明作用域
- UnsafeMutableRawPointer.allocate 創(chuàng)建分配所需字節(jié)數(shù)喇嘱,該指針可以用來讀取和存儲(chǔ)(改變)原生的字節(jié)。
這里分配內(nèi)存使用了allocate 方法:
public static func allocate(byteCount: Int, alignment: Int) -> UnsafeMutableRawPointer
- byteCount:所需字節(jié)數(shù)
- alignment:內(nèi)存對(duì)齊
- 延時(shí)釋放
使用 defer 來保證內(nèi)存得到正確地釋放塞栅,操作指針的時(shí)候者铜,所有內(nèi)存都需要我們手動(dòng)進(jìn)行管理。
這里釋放內(nèi)存使用了deallocate方法:
- 延時(shí)釋放
public func deallocate()
allocate 和 deallocate 方法一定要配對(duì)出現(xiàn)放椰。
- 使用 storeBytes 和 load 方法存儲(chǔ)和讀取字節(jié)
存儲(chǔ)數(shù)據(jù)方法storeBytes:
/// - Parameters:
/// - value: The value to store as raw bytes.
/// - offset: The offset from this pointer, in bytes. `offset` must be
/// nonnegative. The default is zero.
/// - type: The type of `value`.
public func storeBytes<T>(of value: T, toByteOffset offset: Int = default, as type: T.Type)
- value:要存儲(chǔ)的值
- offset:偏移量作烟,默認(rèn)即可
- type:值的類型
讀取數(shù)據(jù)方法 load :
/// - Parameters:
/// - offset: The offset from this pointer, in bytes. `offset` must be
/// nonnegative. The default is zero.
/// - type: The type of the instance to create.
/// - Returns: A new instance of type `T`, read from the raw bytes at
/// `offset`. The returned instance is memory-managed and unassociated
/// with the value in the memory referenced by this pointer.
public func load<T>(fromByteOffset offset: Int = default, as type: T.Type) -> T
- offset:偏移量,默認(rèn)即可
- type:值的類型
移動(dòng)指針地址 advanced :
/// - Parameter n: The number of bytes to offset this pointer. `n` may be
/// positive, negative, or zero.
/// - Returns: A pointer offset from this pointer by `n` bytes.
public func advanced(by n: Int) -> UnsafeMutableRawPointer
- n:步長stride
使用原生指針砾医,存儲(chǔ)下一個(gè)值的時(shí)候需要移動(dòng)一個(gè)步長(stride)拿撩,也可以直接使用 + 運(yùn)算符:
(pointer + stride).storeBytes(of: 6, as: Int.self)
- UnsafeRawBufferPointer 類型以字節(jié)流的形式來讀取內(nèi)存。這意味著我們可以這些字節(jié)進(jìn)行迭代如蚜,對(duì)其使用下標(biāo)压恒,或者使用 filter影暴,map 以及 reduce 這些很酷的方法,緩沖類型指針使用了原生指針進(jìn)行初始化探赫。
類型指針
do {
print("Typed pointers")
// 1.
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
pointer.initialize(repeating: 0, count: count)
// 2.
defer {
pointer.deinitialize(count: count)
pointer.deallocate()
}
// 3.
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)")
}
}
類型指針與原生指針的區(qū)別型宙,主要體現(xiàn)在上面標(biāo)注數(shù)字的幾個(gè)地方:
- 分配內(nèi)存、初始化
類型指針伦吠,在分配內(nèi)存的時(shí)候通過給范型賦值來指定當(dāng)前指針?biāo)僮鞯臄?shù)據(jù)類型:
/// - Parameter count: The amount of memory to allocate, counted in instances
/// of `Pointee`.
public static func allocate(capacity count: Int) -> UnsafeMutablePointer<Pointee>
- count:要存儲(chǔ)的數(shù)據(jù)個(gè)數(shù)
可以看到其分配內(nèi)存的方法妆兑,只有一個(gè)參數(shù),指定所要存儲(chǔ)的數(shù)據(jù)個(gè)數(shù)即可毛仪,因?yàn)橥ㄟ^給范型參數(shù)賦值搁嗓,已經(jīng)知道了要存儲(chǔ)的數(shù)據(jù)類型,其alignment和stride就確定了箱靴,這時(shí)只需要再知道存儲(chǔ)幾個(gè)數(shù)據(jù)即可腺逛。
這里還多了個(gè)初始化的過程,類型指針單單分配內(nèi)存刨晴,還不能使用屉来,還需要初始化:
/// - Parameters:
/// - repeatedValue: The instance to initialize this pointer's memory with.
/// - count: The number of consecutive copies of `newValue` to initialize.
/// `count` must not be negative.
public func initialize(repeating repeatedValue: Pointee, count: Int)
- repeatedValue:默認(rèn)值
- count:數(shù)量
- 延時(shí)釋放
在釋放的時(shí)候,要先釋放已初始化的實(shí)例(deinitialize)狈癞,再釋放已分配的內(nèi)存(deallocate)空間:
/// - Parameter count: The number of instances to deinitialize. `count` must
/// not be negative.
/// - Returns: A raw pointer to the same address as this pointer. The memory
/// referenced by the returned raw pointer is still bound to `Pointee`.
public func deinitialize(count: Int) -> UnsafeMutableRawPointer
- count:數(shù)量
- 存儲(chǔ)/讀取
類型指針的存儲(chǔ)/讀取值茄靠,不需要再使用storeBytes/load,Swift提供了一個(gè)以類型安全的方式讀取和存儲(chǔ)值--pointee:
- 存儲(chǔ)/讀取
public var pointee: Pointee { get nonmutating set }
這里的移動(dòng)指針的方法蝶桶,和上面的一致慨绳,也是 advanced ,但是其參數(shù)有所不同:
/// - Parameter n: The number of strides of the pointer's `Pointee` type to
/// offset this pointer. To access the stride, use
/// `MemoryLayout<Pointee>.stride`. `n` may be positive, negative, or
/// zero.
/// - Returns: A pointer offset from this pointer by `n` instances of the
/// `Pointee` type.
public func advanced(by n: Int) -> UnsafeMutablePointer<Pointee>
- n:這里是按類型值的個(gè)數(shù)進(jìn)行移動(dòng)
同樣真竖,這里也可以使用運(yùn)算符 + 進(jìn)行移動(dòng):
(pointer + 1).pointee = 6
原生指針轉(zhuǎn)換為類型指針
do {
print("Converting raw pointers to typed pointers")
// 創(chuàng)建原生指針
let rawPointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: aligment)
// 延遲釋放原生指針的內(nèi)存
defer {
rawPointer.deallocate()
}
// 將原生指針綁定類型
let typePointer = rawPointer.bindMemory(to: Int.self, capacity: count)
typePointer.initialize(repeating: 0, count: count)
defer {
typePointer.deinitialize(count: count)
}
typePointer.pointee = 42
typePointer.advanced(by: 1).pointee = 9
typePointer.pointee
typePointer.advanced(by: 1).pointee
let bufferPointer = UnsafeBufferPointer(start: typePointer, count: count)
for (index, value) in bufferPointer.enumerated() {
print("value \(index): \(value)")
}
}
原生指針轉(zhuǎn)換為類型指針脐雪,是通過調(diào)用內(nèi)存綁定到特定的類型來完成的:
/// - Parameters:
/// - type: The type `T` to bind the memory to.
/// - count: The amount of memory to bind to type `T`, counted as instances
/// of `T`.
/// - Returns: A typed pointer to the newly bound memory. The memory in this
/// region is bound to `T`, but has not been modified in any other way.
/// The number of bytes in this region is
/// `count * MemoryLayout<T>.stride`.
public func bindMemory<T>(to type: T.Type, capacity count: Int) -> UnsafeMutablePointer<T>
- type:數(shù)據(jù)類型
- count:容量
通過對(duì)內(nèi)存的綁定,我們可以通過類型安全的方法來訪問它恢共。其實(shí)我們手動(dòng)創(chuàng)建類型指針的時(shí)候战秋,系統(tǒng)自動(dòng)幫我們進(jìn)行了內(nèi)存綁定。
獲取一個(gè)實(shí)例的字節(jié)
這里定義了一個(gè)結(jié)構(gòu)體 Sample來作為示例:
struct Sample {
var number: Int
var flag: Bool
init(number: Int, flag: Bool) {
self.number = number
self.flag = flag
}
}
do {
print("Getting the bytes of an instance")
var sample = Sample(number: 25, flag: true)
// 1.
withUnsafeBytes(of: &sample) { (rs) in
for bute in rs {
print(bute)
}
}
}
這里主要是使用了withUnsafeBytes 方法來實(shí)現(xiàn)獲取字節(jié)數(shù):
/// - Parameters:
/// - arg: An instance to temporarily access through a raw buffer pointer.
/// - body: A closure that takes a raw buffer pointer to the bytes of `arg`
/// as its sole argument. If the closure has a return value, that value is
/// also used as the return value of the `withUnsafeBytes(of:_:)`
/// function. The buffer pointer argument is valid only for the duration
/// of the closure's execution.
/// - Returns: The return value, if any, of the `body` closure.
public func withUnsafeBytes<T, Result>(of arg: inout T, _ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result
- arg:實(shí)例對(duì)象地址
- body:回調(diào)閉包讨韭,參數(shù)為UnsafeRawBufferPointer 類型的指針
注意:該方法和回調(diào)閉包都有返回值脂信,如果閉包有返回值,此返回值將會(huì)作為該方法的返回值透硝;但是狰闪,一定不要在閉包中將body的參數(shù),即:UnsafeRawBufferPointer 類型的指針作為返回值返回濒生,該參數(shù)的使用范圍僅限當(dāng)前閉包埋泵,該參數(shù)的使用范圍僅限當(dāng)前閉包,該參數(shù)的使用范圍僅限當(dāng)前閉包。
withUnsafeBytes 同樣適合用 Array 和 Data 的實(shí)例.
使用指針的原則
不要從 withUnsafeBytes 中返回指針
絕對(duì)不要讓指針逃出 withUnsafeBytes(of:) 的作用域范圍丽声。這樣的代碼會(huì)成為定時(shí)炸彈礁蔗,你永遠(yuǎn)不知道它什么時(shí)候可以用,而什么時(shí)候會(huì)崩潰雁社。
一次只綁定一種類型
在使用 bindMemory方法將原生指針綁定內(nèi)存類型瘦麸,轉(zhuǎn)為類型指針的時(shí)候,一次只能綁定一個(gè)類型歧胁,例如:將一個(gè)原生指針綁定Int類型,不能再綁定Bool類型:
let typePointer = rawPointer.bindMemory(to: Int.self, capacity: count)
// 一定不要這么做
let typePointer1 = rawPointer.bindMemory(to: Bool.self, capacity: count)
但是厉碟,我們可以使用 withMemoryRebound 來對(duì)內(nèi)存進(jìn)行重新綁定喊巍。并且,這條規(guī)則也表明了不要將一個(gè)基本類型(如 Int)重新綁定到一個(gè)自定義類型(如 class)上驶冒。
/// - Parameters:
/// - type: The type to temporarily bind the memory referenced by this
/// pointer. The type `T` must be the same size and be layout compatible
/// with the pointer's `Pointee` type.
/// - count: The number of instances of `T` to bind to `type`.
/// - body: A closure that takes a mutable typed pointer to the
/// same memory as this pointer, only bound to type `T`. The closure's
/// pointer argument is valid only for the duration of the closure's
/// execution. If `body` has a return value, that value is also used as
/// the return value for the `withMemoryRebound(to:capacity:_:)` method.
/// - Returns: The return value, if any, of the `body` closure parameter.
public func withMemoryRebound<T, Result>(to type: T.Type, capacity count: Int, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result
- type:值的類型
- count:值的個(gè)數(shù)
- body:回調(diào)閉包扶认,參數(shù)為UnsafeRawBufferPointer 類型的指針
注意:該方法和回調(diào)閉包都有返回值声诸,如果閉包有返回值,此返回值將會(huì)作為該方法的返回值何暮;但是,一定不要在閉包中將body的參數(shù)铐殃,即:UnsafeRawBufferPointer 類型的指針作為返回值返回海洼,該參數(shù)的使用范圍僅限當(dāng)前閉包,該參數(shù)的使用范圍僅限當(dāng)前閉包富腊,該參數(shù)的使用范圍僅限當(dāng)前閉包坏逢。
不要操作超出范圍的內(nèi)存
do {
let count = 3
let stride = MemoryLayout<Int16>.stride
let alignment = MemoryLayout<Int16>.alignment
let byteCount = count * stride
let pointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)
// 1. 這里的count+1,超出了原有指針pointer分配的內(nèi)存范圍
let bufferPointer = UnsafeRawBufferPointer.init(start: pointer, count: count + 1)
for byte in bufferPointer {
print(byte)
}
}
這里的count+1赘被,超出了原有指針pointer分配的內(nèi)存范圍是整,切記不要出現(xiàn)這種情況。