一五鲫、Swift指針
1.Swift指針簡介
swift中的指針分為兩類
typed pointer
指定數(shù)據(jù)類型
指針雏掠,即UnsafePointer<T>
,其中T表示泛型raw pointer
未指定數(shù)據(jù)類型
的指針(原生指針) 侧但,即UnsafeRawPointer
swift與OC指針對比如下:
Swift | OC | 說明 |
---|---|---|
UnsafePointer<T> | const T * | 指針及所指向的內(nèi)容都不可變 |
UnsafeMutablePointer<T> | T * | 指針及其所指向的內(nèi)存內(nèi)容均可變 |
UnsafeRawPointer | const void * | 指針指向不可變未知類型 |
UnsafeMutableRawPointer | void * | 指針指向可變未知類型 |
2.使用
2.1 raw pointer
注: 對于raw pointer夺谁,其內(nèi)存管理是手動管理的,指針在使用完畢后需要手動釋放
// 定義一個未知類型的的指針p础芍,分配32字節(jié)大小的空間灸叼,8字節(jié)對齊
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
// 存儲數(shù)據(jù)
for i in 0..<4 {
p.storeBytes(of: i + 1, as: Int.self)
}
// 讀取數(shù)據(jù)
for i in 0..<4 {
// 從首地址開始偏移讀取
let value = p.load(fromByteOffset: i * 8, as: Int.self)
print("index: \(i) value: \(value)")
}
// 釋放內(nèi)存
p.deallocate()
//index: 0 value: 4
//index: 1 value: -5764607523034234880
//index: 2 value: 4461297666
//index: 3 value: 668503133614176
從運行結(jié)果中可以看到神汹,這并不是我們想要的結(jié)果,這也是我們平常在使用的時候需要特別注意的古今。因為我們在讀取的時候是從指針首地址進行不斷的偏移讀取的屁魏,但是存儲的時候卻都是存儲在了首地址,所以存儲的時候也要進行偏移捉腥。修改后的代碼如下:
// 定義一個未知類型的的指針p氓拼,分配32字節(jié)大小的空間,8字節(jié)對齊
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
// 存儲數(shù)據(jù)
for i in 0..<4 {
// p.storeBytes(of: i + 1, as: Int.self)
// 修改后(每次存儲的位置都有增加抵碟,也就是偏移)
p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
}
// 讀取數(shù)據(jù)
for i in 0..<4 {
// 從首地址開始偏移讀取
let value = p.load(fromByteOffset: i * 8, as: Int.self)
print("index: \(i) value: \(value)")
}
// 釋放內(nèi)存
p.deallocate()
//index: 0 value: 1
//index: 1 value: 2
//index: 2 value: 3
//index: 3 value: 4
allocate 源碼
/// Allocates uninitialized memory with the specified size and alignment.
///
/// You are in charge of managing the allocated memory. Be sure to deallocate
/// any memory that you manually allocate.
///
/// The allocated memory is not bound to any specific type and must be bound
/// before performing any typed operations. If you are using the memory for
/// a specific type, allocate memory using the
/// `UnsafeMutablePointer.allocate(capacity:)` static method instead.
///
/// - Parameters:
/// - byteCount: The number of bytes to allocate. `byteCount` must not be negative.
/// - alignment: The alignment of the new region of allocated memory, in
/// bytes.
/// - Returns: A pointer to a newly allocated region of memory. The memory is
/// allocated, but not initialized.
@inlinable
public static func allocate(
byteCount: Int, alignment: Int
) -> UnsafeMutableRawPointer {
// For any alignment <= _minAllocationAlignment, force alignment = 0.
// This forces the runtime's "aligned" allocation path so that
// deallocation does not require the original alignment.
//
// The runtime guarantees:
//
// align == 0 || align > _minAllocationAlignment:
// Runtime uses "aligned allocation".
//
// 0 < align <= _minAllocationAlignment:
// Runtime may use either malloc or "aligned allocation".
var alignment = alignment
if alignment <= _minAllocationAlignment() {
alignment = 0
}
return UnsafeMutableRawPointer(Builtin.allocRaw(
byteCount._builtinWordValue, alignment._builtinWordValue))
}
- 以指定的大小和對齊方式分配未初始化的內(nèi)存
- 首先對對齊方式進行校驗
- 然后調(diào)用
Builtin.allocRaw
方法進行分配內(nèi)存 -
Builtin
是Swift
的標(biāo)準(zhǔn)模塊桃漾,可以理解為調(diào)用(匹配)LLVM
中的方法
2.2 typed pointer
定義
/// Invokes the given closure with a pointer to the given argument.
///
/// The `withUnsafePointer(to:_:)` function is useful for calling Objective-C
/// APIs that take in parameters by const pointer.
///
/// The pointer argument to `body` is valid only during the execution of
/// `withUnsafePointer(to:_:)`. Do not store or return the pointer for later
/// use.
///
/// - Parameters:
/// - value: An instance to temporarily use via pointer.
/// - body: A closure that takes a pointer to `value` as its sole argument. If
/// the closure has a return value, that value is also used as the return
/// value of the `withUnsafePointer(to:_:)` function. The pointer argument
/// is valid only for the duration of the function's execution.
/// It is undefined behavior to try to mutate through the pointer argument
/// by converting it to `UnsafeMutablePointer` or any other mutable pointer
/// type. If you need to mutate the argument through the pointer, use
/// `withUnsafeMutablePointer(to:_:)` instead.
/// - Returns: The return value, if any, of the `body` closure.
@inlinable public func withUnsafePointer<T, Result>(to value: T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
該函數(shù)一共兩個參數(shù):
- 第一個就是要獲取其指針的變量
- 第二個是一個閉包,然后通過rethrows關(guān)鍵字重新拋出Result(也就是閉包表達式的返回值)拟逮,閉包的參數(shù)和返回值都是泛型撬统,關(guān)于這種寫法可以縮寫,詳見后面的代碼敦迄。
var a = 10
/**
通過Swift提供的簡寫的API恋追,這里是尾隨閉包的寫法
返回值的類型是 UnsafePointer<Int>
*/
let p = withUnsafePointer(to: &a) { $0 }
print(p)
withUnsafePointer(to: &a) {
print($0)
}
// Declaration let p1:UnsafePointer<Int>
let p1 = withUnsafePointer(to: &a) { ptr in
return ptr
}
print(p1)
//0x00007ff7ba577d18
//0x00007ff7ba577d18
//0x00007ff7ba577d18
以上三種用法是我們最常用的三種方法,都能夠打印出變量的指針罚屋。那么是否可以通過指針修改變量的值呢苦囱?下面我們就來研究一下:
通過指針獲取變量值
要想改變值,首先就要能夠訪問到變量的值:
let p = withUnsafePointer(to: &a) { $0 }
print(p.pointee)
withUnsafePointer(to: &a) {
print($0.pointee)
}
let p1 = withUnsafePointer(to: &a) { ptr in
return ptr
}
print(p1.pointee)
let p2 = withUnsafePointer(to: &a) { ptr in
return ptr.pointee
}
print(p2)
//10
//10
//10
//10
通過指針修改變量值
如果使用的是withUnsafePointer是不能直接在閉包中修改指針的脾猛,但是我們可以通過間接的方式撕彤,通過返回值修改,給原來變量賦值的方式修改(其實這種方式很low)
a = withUnsafePointer(to: &a){ ptr in
return ptr.pointee + 2
}
print(a)
我們可以使用withUnsafeMutablePointer
猛拴,直接修改變量的值喉刘。
withUnsafeMutablePointer(to: &a){ ptr in
ptr.pointee += 2
}
還有另一種方式瞧柔,就是通過創(chuàng)建指針的方式,這也是一種創(chuàng)建Type Pointer的方式:
// 創(chuàng)建一個指針睦裳,指針內(nèi)存存儲的是Int類型數(shù)據(jù),開辟一個8*1字節(jié)大小的區(qū)域
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
//初始化指針
ptr.initialize(to: a)
// 修改
ptr.pointee += 2
print(a)
print(ptr.pointee)
// 反初始化撼唾,與下面的代碼成對調(diào)用廉邑,管理內(nèi)存
ptr.deinitialize(count: 1)
// 釋放內(nèi)存
ptr.deallocate()
//10
//12
從這里我們可以看到,指針的值在修改后是變了的倒谷,但是原變量的值并沒有改變蛛蒙。所以不能用于直接修改原變量。
3.實例
demo1
本案例是初始化一個指針渤愁,能夠訪問兩個結(jié)構(gòu)體實例對象牵祟。
首先定義一個結(jié)構(gòu)體
struct Teacher {
var age = 18
var height = 1.65
}
下面我們通過三種方式訪問指針中的結(jié)構(gòu)體對象
- 通過下標(biāo)訪問
- 通過內(nèi)存平移訪問
- 通過successor()函數(shù)訪問
// 分配兩個Teacher大小空間的指針
let ptr = UnsafeMutablePointer<Teacher>.allocate(capacity: 2)
// 初始化第一個Teacher
ptr.initialize(to: Teacher())
// 初始化第二個Teacher
ptr.successor().initialize(to: Teacher(age: 20, height: 1.85))
// 錯誤的初始化方式,因為這是確定類型的指針抖格,只需移動一步即移動整個類型大小的內(nèi)存
//ptr.advanced(by: MemoryLayout<Teacher>.stride).initialize(to: Teacher(age: 20, height: 1.85))
// 通過下標(biāo)訪問
print(ptr[0])
print(ptr[1])
// 內(nèi)存偏移
print(ptr.pointee)
print((ptr+1).pointee)
// successor
print(ptr.pointee)
print(ptr.successor().pointee)
// 反初始化诺苹,釋放內(nèi)存
ptr.deinitialize(count: 2)
ptr.deallocate()
//Teacher(age: 18, height: 1.65)
//Teacher(age: 20, height: 1.85)
//Teacher(age: 18, height: 1.65)
//Teacher(age: 20, height: 1.85)
//Teacher(age: 18, height: 1.65)
//Teacher(age: 20, height: 1.85)
demo2
下面我們就通過內(nèi)存的方式,將實例對象綁定到我們自定義的HHObject
上雹拄。
struct HHObject{
var kind: UnsafeRawPointer
}
class Teachers {
var age: Int = 18
}
指針綁定:
/**
使用withUnsafeMutablePointer獲取到的指針是UnsafeMutablePointer<T>類型
UnsafeMutablePointer<T>沒有bindMemory方法
所以此處引入Unmanaged
*/
//let ptr = withUnsafeMutablePointer(to: &t) { $0 }
/**
Unmanaged 指定內(nèi)存管理收奔,類似于OC與CF交互時的所有權(quán)轉(zhuǎn)換__bridge
Unmanaged 有兩個函數(shù):
- passUnretained:不增加引用計數(shù),也就是不獲得所有權(quán)
- passRetained: 增加引用計數(shù)滓玖,也就是可以獲得所有權(quán)
以上兩個函數(shù)坪哄,可以通過toOpaque函數(shù)返回一個
UnsafeMutableRawPointer 指針
*/
let ptr4 = Unmanaged.passUnretained(Teachers()).toOpaque()
//let ptr = Unmanaged.passRetained(t).toOpaque()
/**
bindMemory :將指針綁定到指定類型數(shù)據(jù)上
如果沒有綁定則綁定
已綁定則重定向到指定類型上
*/
let h = ptr4.bindMemory(to: HHObject.self, capacity: 1)
print(h.pointee)
//HHObject(kind: 0x00007efc5e4d0e60)
demo3
在實際開發(fā)中,我們往往會調(diào)用其他人的api完成一些代碼势篡,在指針操作過程中翩肌,往往會因為類型不匹配造成傳參問題,下面我們就來看一個例子:
首先我們定義一個打印指針的函數(shù):
func printPointer(p: UnsafePointer<Int>) {
print(p)
print("end")
}
示例代碼:
var tul = (10,20)
withUnsafeMutablePointer(to: &tul) { tulPtr in
printPointer(p: UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
}
//0x00007ff7b564cc60
//end
assumingMemoryBound
是假定內(nèi)存綁定禁悠,目的是告訴編譯器已經(jīng)綁定過Int
類型了念祭,不需要在檢查內(nèi)存綁定。
那么我們將元組換成結(jié)構(gòu)體呢绷蹲?我們此時想要獲取結(jié)構(gòu)體中的屬性棒卷。
struct Test {
var a: Int = 10
var b: Int = 20
}
var t = Test()
withUnsafeMutablePointer(to: &t) { ptr in
printPointer(p: UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self))
}
//0x00007ff7b77b8c50
//end
那么如果我想想獲取結(jié)構(gòu)體中的屬性呢?
withUnsafeMutablePointer(to: &t) { ptr in
// let strongRefPtr = withUnsafePointer(to: &ptr.pointee.b) { $0 }
// printPointer(p: strongRefPtr)
// let strongRefPtr = UnsafeRawPointer(ptr).advanced(by: MemoryLayout<Int>.stride)
let strongRefPtr = UnsafeRawPointer(ptr) - MemoryLayout<Test>.offset(of: \Test.b)!
printPointer(p: strongRefPtr.assumingMemoryBound(to: Int.self))
}
以上提供了三種方式實現(xiàn)訪問結(jié)構(gòu)體中的屬性祝钢。
demo 4
使用withMemoryRebound
臨時更改內(nèi)存綁定類型比规,withMemoryRebound
的主要應(yīng)用場景還是處理一些類型不匹配的場景,將內(nèi)存綁定類型臨時修改成想要的類型拦英,在閉包里生效蜒什,不會修改原指針的內(nèi)存綁定類型。
var age: UInt64 = 18
let ptr = withUnsafePointer(to: &age) { $0 }
// 臨時更改內(nèi)存綁定類型
ptr.withMemoryRebound(to: Int.self, capacity: 1) { (ptr) in
printPointer(p: ptr)
}
func printPointer(p: UnsafePointer<Int>) {
print(p)
print("end")
}
4.總結(jié)
至此我們對Swift中指針的分析基本就完事了疤估,現(xiàn)在總結(jié)如下:
- Swift中的指針分為兩種:
-
raw pointer
:未指定類型(原生)指針灾常,即:UnsafeRawPointer
和unsafeMutableRawPointer
-
type pointer
:指定類型的指針霎冯,即:UnsafePointer<T>
和UnsafeMutablePointer<T>
- 指針的綁定主要有三種方式:
-
withMemoryRebound
:臨時更改內(nèi)存綁定類型 -
bingMemory(to: capacity:)
:更改內(nèi)存綁定的類型,如果之前沒有綁定钞瀑,那么就是首次綁定沈撞,如果綁定過了,會被重新綁定為該類型 -
assumingMemoryBound
:假定內(nèi)存綁定雕什,這里就是告訴編譯器缠俺,我就是這種類型,不要檢查了(控制權(quán)由我們決定)
- 內(nèi)存指針的操作都是危險的贷岸,使用時要慎重
二壹士、內(nèi)存管理
跟OC一樣,Swift也是采取基于引用計數(shù)的ARC內(nèi)存管理方案(針對堆空間)偿警,Swift的ARC中有3種引用躏救。
1.弱引用
protocol Livable : AnyObject {}
class Person {}
weak var p0: Person?
weak var p1: AnyObject?
weak var p2: Livable?
unowned var p10: Person?
unowned var p11: AnyObject?
unowned var p12: Livable?
說明:
- weak、unowned只能用在類實例上面
- 因為weak可以設(shè)置為nil螟蒸,所以必須用可選項
- 會發(fā)生改變盒使,所以需要用var
2.循環(huán)引用
循環(huán)引用就是兩個對象相互持有,無法釋放會導(dǎo)致內(nèi)存泄漏尿庐,和OC一樣忠怖,所以重點看一下閉包的循環(huán)引用,其實也就和block一樣
循環(huán)引用的解決:weak抄瑟、unowned 都能解決循環(huán)引用的問題凡泣,unowned 要比 weak 少一些性能消耗,在生命周期中可能會變?yōu)?nil 的使用 weak皮假,初始化賦值后再也不會變?yōu)?nil 的使用unowned