Swift Pointer 使用指南


Overview

C Syntax Swift Syntax Note
const Type * UnsafePointer<Type> 指針可變,指針指向的內存值不可變。
Type * UnsafeMutablePointer<Type> 指針和指針指向的內存值均可變。
ClassType * const * UnsafePointer<ClassType> 指針的指針:指針不可變,指針指向的類可變。
ClassType * __strong * UnsafeMutablePointer<ClassType> 指針的指針:指針和指針指向的類均可變。
ClassType ** AutoreleasingUnsafeMutablePointer<Type>  
const void * UnsafeRawPointer 指針指向的內存區類型未定。
void * UnsafeMutableRawPointer 同上
StructType * OpaquePointer C 語言中的一些自定義類型,Swift 中並未有相對應的類型。
int8_t a[] var x:[Int8] -> UnsafeBufferPointer Buffer 一詞不難聯想到數組

對於聲明為 UnsafePointer<Type>作為參數的函數,同樣可以接受如下類型:

  • UnsafePointer<Type>UnsafeMutablePointer<Type>AutoreleaseingUnsafeMutablePointer<Type>, 以上類型可轉成 UnsafePointer<Type>
  • String值,如果Type為 Int8或 Uint8,那么字符串將被轉成 UTF8 數組。要知道底層本質還是以字節為單位。
  • 對於[Type]數組,指針指向數組的第一個元素地址。

基礎用法

Example01- 定義一個指針:

C :

int *a = malloc(sizeof(int)); *a = 42; printf("a's value: %d", *a); free(a) 

Swift :

let a = UnsafeMutablePointer<Int>.allocate(capacity: 1) a.pointee = 42 print("a's value: \(a.pointee)") // 42 a.deallocate(capacity: 1) 

pointee可理解為解引(dereference),即用 *符號獲得指針指向內存區域的值。

取址方式一致,使用 &獲得變量的內存地址:

var a = 42 functionAcceptVariableAddress(&a) // 這里函數接受一個類型為 UnsafeMutablePointer<Int> 傳參 

Example02 - 操作指針指向的值:

C :

int *a = malloc(sizeof(int)); *a = 42; *a += 100; // 先用 * 解引(dereference) 然后對指針指向的內存值做加法 

Swift :

var a = 42 withUnsafeMutablePointer(to: &a) { $0.pointee += 100 } // 要修改指針指向的內存值 所以用MutablePointer 

關鍵的兩個指針操作函數withUnsafeMutablePointerwithUnsafePointer

/// Invokes `body` with an `UnsafeMutablePointer` to `arg` and returns the /// result. Useful for calling Objective-C APIs that take "in/out" /// parameters (and default-constructible "out" parameters) by pointer. public func withUnsafeMutablePointer<T, Result>(to arg: inout T, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result /// Invokes `body` with an `UnsafePointer` to `arg` and returns the /// result. Useful for calling Objective-C APIs that take "in/out" /// parameters (and default-constructible "out" parameters) by pointer. public func withUnsafePointer<T, Result>(to arg: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result 

使用了泛型(generic) :T 和 Result。T 標示傳入類型,即 a的類型 Int,由於標示了修飾關鍵字 inout,所以傳入參數為指針(用&取址) ; Body閉包的傳參為 T類型指針,返回值類型為Result,推斷可得還是 Int類型。閉包中對指針指向的內存區域進行直接操作,這里閉包中的$0為 arg的地址。


Example03 - 獲取字符串中的字符 :

C :

char string[] = "hello"; printf("%c\n",string[0]); // 輸出'h' 

Swift :

var string = "hello" // 5個字符 'h' 'e' 'l' 'l' 'o' 每個字符占一個字節 var strdata = string.data(using: .ascii) strdata?.withUnsafeBytes({ (ptr:UnsafePointer<Int8>) in print(ptr.pointee) // 104 = 'h' }) 

Example04 - 自定義類型指針之間的轉換:

C 以自定義結構體為例:

struct MyStruct { char name[6]; // 4個字節 int16_t reserved; // 保留 }; struct MyStruct1 { char firstname[2]; // 2個字節 姓 char lastname[4]; // 2個字節 名 int16_t reserved; // 保留 }; int main(int argc, const char * argv[]) { struct MyStruct myInfo = { .name = "machao", .reserved = 0x00000, }; printf("my name is %s \n",myInfo.name); struct MyStruct1 *myInfo2 = (struct MyStruct1 *)&myInfo; printf("my lastname is %s",myInfo2->lastname); return 0; } 

Swift :

struct MyStruct {
    var name:(Int8, Int8, Int8, Int8, Int8, Int8) = (0x6d, 0x61, 0x63,0x68, 0x61, 0x6F)// 109(m) 97(a) 99(c) 104(h) 97(a) 111(o) var reserved:Int16 = 0x0000 // 保留 } struct MyStruct1 { var firstname:(Int8, Int8) // 2個字節 姓 var lastname:(Int8, Int8, Int8, Int8) // 4個字節 名 var reserved:Int16 // 保留 } var struct1 = MyStruct() withUnsafePointer(to: &struct1){ ptr in ptr.withMemoryRebound(to: MyStruct1.self, capacity: 1, { ptrStruct1 in print("firstname \(ptrStruct1.pointee.firstname)") print("lastname \(ptrStruct1.pointee.lastname)") }) } 

to為要 cast 過去的Type,這里是MyStruct1capacity為整體容量,而非結構體總字節數;ptrStruct1為 &struct1指針轉成 MyStruct類型后的指針,你可以直接通過它來�操作,可以看到我們用 pointee解引取到結構體,接着就可以隨心所欲的操作里面的成員了。

Example05 - 將具有明確類型的指針轉成 Void *指針:

C :

int *a = 10; void *ptr = (void *)a; // 注意一個強轉就搞定了 

Swift :

let intPtr = UnsafeMutablePointer<Int>.allocate(capacity: 1) let voidPtr = UnsafeRawPointer(intPtr) let intPtrAgain = voidPtr.assumingMemoryBound(to: Int.self) 

assumingMemoryBound方法從字面理解即假設指針指向區域的內存大小,這里為Int.self,4個字節。

Example06 - 數組操作:

此處不給出 C 語言操作例程。

Swift:

var array = [1,2,3,4] var ptr = UnsafeBufferPointer(start: &array, count: 4) ptr.forEach { print("\($0)") } 

實戰

socket 聊天室

服務端代碼:


import Foundation import Darwin /// ================= 客戶端運行程序 ================= /// 創建 socket 接口 var sockfd_client:Int32 /// 用於bind服務端信息的 protofamily AN_INIT 決定ipv4(32位IP地址)和端口號(16位)的組合 var server_addr:sockaddr_in // 第一步:應對服務器的 socket 參數分別是:協議域(族) socket類型 協議 sockfd_client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) // 第二步:填充服務器socket的信息 server_addr = sockaddr_in() server_addr.sin_family = sa_family_t(AF_INET) let _ = "127.0.0.1".data(using: .ascii)?.withUnsafeBytes({ (ptr: UnsafePointer<Int8>) in inet_pton(AF_INET, ptr, &server_addr.sin_addr.s_addr) }) server_addr.sin_port = 8000 // 第三步:連接服務器 自動分配一個端口 let _ = withUnsafePointer(to: &server_addr){ ptr in ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { ptrSockAddr in connect(sockfd_client, UnsafePointer(ptrSockAddr), socklen_t(MemoryLayout<sockaddr>.size)) } } var message = "12345678" let _ = message.data(using: .ascii)?.withUnsafeBytes({ (ptr:UnsafePointer<Int8>)in // 第四步: 向服務器發送一條信息 send(sockfd_client, ptr, message.characters.count, 0) }) close(sockfd_client) 

客戶端:


import Foundation import Darwin /// ================= 服務端運行程序 ================= /// 用於創建 listen 的 socket 接口 var socket_fd:Int32 /// 用於bind服務端信息的 protofamily AN_INIT 決定ipv4(32位IP地址)和端口號(16位)的組合 var server_addr:sockaddr_in // 第一步:創建一個 socket 參數分別是:協議域(族) socket類型 協議 socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) // 第二步:創建一個 socket描述 填充具體的協議內容 包括端口號 IP server_addr = sockaddr_in() server_addr.sin_family = sa_family_t(AF_INET) server_addr.sin_addr.s_addr = INADDR_ANY // 設置IP 系統自動獲取本機的IP地址 server_addr.sin_port = 8000 // 第三步:bind 這里需要將sockaddr_in * 強轉成 sockaddr let _ = withUnsafePointer(to: &server_addr){ ptr in ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { ptrSockAddr in bind(socket_fd, UnsafePointer(ptrSockAddr), socklen_t(MemoryLayout<sockaddr>.size)) } } // 第四步:開啟監聽 listen(socket_fd,10) print("▶️ 開啟監聽...") // 第五步:接受客戶端發起的請求 var connect_fd: Int32 // 用於記錄連接的客戶端socket描述符 var connect_addrin: sockaddr = sockaddr()// 記錄連接客戶端的 ip port 等信息 var connect_length: socklen_t = 0// 記錄socket地址的長度 var n : Int var buffer: [UInt8] = [UInt8](repeating: 0, count: 4096) repeat { connect_fd = accept(socket_fd, &connect_addrin, &connect_length) guard connect_fd != -1 else { continue } // 接受客戶端過來的數據 n = recv(connect_fd, &buffer, 4096, 0) print("recv \(buffer)") // 關閉連接 close(connect_fd); } while true 

文獻



作者:NinthDay
鏈接:https://www.jianshu.com/p/8217bf3444a8
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM