Swift中指針UnsafePointer的常見用法

指針類型
//基本指針
UnsafePointer<T>                    const T *
UnsafeMutablePointer                T *
//集合指針
UnsafeBufferPointer                 const T * //指向一個連續已知類型區域,可以看成一個集合,並支持集合操作
UnsafeMutableBufferPointer          T * //指向一個連續已知類型區域,可以看成一個集合,並支持集合操作
//空指針
UnsafeRawPointer                    const void *
UnsafeMutableRawPointer             void *
UnsafeRawBufferPointer              const void * //指向一個連續未知類型區域
UnsafeMutableRawBufferPointer       void *  //指向一個連續未知類型區域

 

UnsafePointer 和 UnsafeMutablePointer
UnsafePointer只讀指針
UnsafeMutablePointer可修改指針

UnsafePointer只讀指針
通過UnsafePointer<T>定義一個類型爲T的指針,通過pointee成員即可獲得T的值。
//UnsafePointer參數只能接收inout修飾的類型,而inout修飾的類型必然是可寫的,所以參數只能用var定義
func call(_ p: UnsafePointer<Int>) {
    print("\(p.pointee)")
}
var a = 1234
//&a用於傳遞指針
call(&a) // 打印:1234
UnsafeMutablePointer可修改指針
可以通過設置p.pointee 修改指針的值。
func modify(_ p: UnsafeMutablePointer<Int>) {
    p.pointee = 5678
}
var a = 1234
modify(&a)
print("\(a)") // 打印:5678
UnsafeBufferPointer 集合指針類型
UnsafeBufferPointer指針實現了Collection協議,因此可以直接使用Collection中的各種方法來遍歷操作數據,filter,map...等。
Swift的數組提供了函數withUnsafeBufferPointer,通過它我們可以方便的用指針來處理數組。
下面通過withUnsafeBufferPointer獲得變量p,p的類型爲UnsafeBufferPointer<Int32>,它代表着一整塊的連續內存,它是集合類型指針,並且它也支持大部分數組操作。
let a: [Int32] = [1, 2, -1, -2, 5, 6]
let p = a.withUnsafeBufferPointer { $0 }
print("\(p.count)") // 打印:6
print("\(p[3])") // 打印:-2
空指針:UnsafeRawPointer
就像C語言有void*(即空指針)一樣,Swift也有自己的空指針,它通過類型UnsafeRawPointer來獲得
let rp: UnsafeMutablePointer<CChar> = rRes.value
let strLength = rRes.length
//將UnsafeMutablePointer<CChar> C語言的char *指針轉成 UnsafeMutableRawPointer 的void *任意指針。然後直接取出封裝成String字符串
let strRes = String.init(bytesNoCopy: UnsafeMutableRawPointer(mutating: rp), length: Int(strLength), encoding: .utf8, freeWhenDone: false) 
指針的類型轉換
UnsafePointer 基本指針
//將指針的類型轉換成一個臨時的給定類型,並將指針傳遞給closure
func withMemoryRebound<T, Result>(to: T.Type, capacity: Int, (UnsafePointer<T>) -> Result) -> Result
//向下移動一位,並返回一個新的指針 
func successor() -> UnsafePointer<Pointee>
//向上移動一位,並返回一個新的指針
func predecessor() -> UnsafePointer<Pointee>

RawPointer 空指針
//轉換給指定類型的指針
func assumingMemoryBound<T>(to: T.Type) -> UnsafeMutablePointer<T>
//轉換成指定類型的指針,capacity指定了這個指針讀取的T數據數量
func bindMemory<T>(to type: T.Type, capacity count: Int) -> UnsafeMutablePointer<T>

UnsafeBufferPointer 集合指針
let rpSub:UnsafeMutablePointer<UInt8> = rp.withMemoryRebound(to: UInt8.self, capacity: Int(strLength), { $0 })
let buffer = UnsafeBufferPointer<UInt8>.init(start: rpSub, count: Int(strLength))

 

指針的操作
指針的輔助函數,通過函數withUnsafePointer,獲得指定類型的對應指針
//返回一個T類型指針。將第一個參數T以指針的形式傳遞給closure
func withUnsafePointer<T, Result>(to: T, (UnsafePointer<T>) -> Result) -> Result
func withUnsafePointer<T, Result>(to: inout T, (UnsafePointer<T>) -> Result) -> Result
func withUnsafeMutablePointer<T, Result>(to: inout T, (UnsafeMutablePointer<T>) -> Result) -> Result
func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
定義一個整形a,而p就是指向a的整形指針,它的類型會被自動轉換爲UnsafePointer<Int>,
第二個參數被簡化爲了{ $0 },代碼塊接收一個UnsafePointer<Int>參數,該參數即是a的地址,直接通過$0將它返回,即得到了a的指針,最終它被傳給了p。
var a = 1234
let p = withUnsafePointer(to: &a) { $0 }
print("\(p.pointee)") // 打印:1234
獲取指針並進行字節級操作 withUnsafeBytes
如果要對某塊內存進行字節級編程。可以通過withUnsafeBytes得到某個類型的數據的字節指針,從而可以對它們進行字節級編程。
因爲withUnsafeBytes返回了一個類型UnsafeRawBufferPointer,該類型代表着一個字節級的內存塊集合指針,所以以通過下標索引、for循環的方式來處理返回的對象。
var a: UInt32 = 0x12345678
let p = withUnsafeBytes(of: &a) { $0 }
var log = ""
for item in p {
    let hex = NSString(format: "%x", item)
    log += "\(hex)"
}
print("\(p.count)") // 打印:4
print("\(log)") // 對於小端機器會打印:78563412

 

通過指針動態創建、銷燬內存
使用Swift可以直接操作內存,如可以開闢和管理一塊內存,最後釋放它,Swift提供了UnsafeMutablePointer的成員函數allocate來處理該工作。
let p = UnsafeMutablePointer<Int32>.allocate(capacity: 1)
p.initialize(to: 0) // 初始化
p.pointee = 32
print("\(p.pointee)") // 打印:32
p.deinitialize(count: 1) // 反初始化
p.deallocate()
C標準庫函數的映射調用
Swift提供了大量的C標準庫的橋接方法,我們可以像調用C語言庫函數一樣它們,如memcpy,strcpy等。
var n = 10086
// malloc
let p = malloc(MemoryLayout<Int32>.size)!
// memcpy
memcpy(p, &n, MemoryLayout<Int32>.size)
let p2 = p.assumingMemoryBound(to: Int32.self)
print("\(p2.pointee)") // 打印:10086
// strcpy
let str = "abc".cString(using: .ascii)!
if str.count != MemoryLayout<Int32>.size {
    return
}
let pstr = p.assumingMemoryBound(to: CChar.self)
strcpy(pstr, str)
print("\(String(cString: pstr))") // 打印:abc
// strlen
print("\(strlen(pstr))") // 打印: 3
// memset
memset(p, 0, MemoryLayout<Int32>.size)
print("\(p2.pointee)") // 打印:0
// strcat
strcat(pstr, "h".cString(using: .ascii)!)
strcat(pstr, "i".cString(using: .ascii)!)
print("\(String(cString: pstr))") // 打印:hi
// strstr
let s = strstr(pstr, "i")!
print("\(String(cString: s))") // 打印:i
// strcmp
print("\(strcmp(pstr, "hi".cString(using: .ascii)!))") // 打印:0
// free
free(p)

 

實際使用案例
1.將C函數庫返回的char *指針,讀取成字符串
let rRes = c_shmread()

let rp: UnsafeMutablePointer<CChar> = rRes.value
let strLength = rRes.length

//1.先將UnsafeMutablePointer<CChar> C語言的char *指針轉成 UnsafeMutableRawPointer 的void *任意指針。然後直接取出封裝成String字符串
if let strRes = String.init(bytesNoCopy: UnsafeMutableRawPointer(mutating: rp), length: Int(strLength), encoding: .utf8, freeWhenDone: false) {
    return strRes
} else {
    return ""
}

//2.使用withMemoryRebound轉換指針類型從CChar轉成UInt8類型;
let rpSub:UnsafeMutablePointer<UInt8> = rp.withMemoryRebound(to: UInt8.self, capacity: Int(strLength), { $0 })
//然後創建集合類型指針UnsafeBufferPointer;
let buffer = UnsafeBufferPointer<UInt8>.init(start: rpSub, count: Int(strLength))
buffer.forEach {
    print($0)
}
//然後創建Data, 再將data轉成String
let data = Data(bytes: rpSub, count: Int(strLength))
let str = String(data: data, encoding: String.Encoding.utf8)
        

 


參考文章:
https://juejin.cn/post/7030789069915291661#heading-6
https://www.jianshu.com/p/a9956ee1ab61
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章