關於Swift中的指標的那些事

chouheiwa發表於2019-01-02

前言

Objective-c的世界中,一切物件都是指標。它是一種執行時語言,具體指標的物件型別將會在執行時,由系統分配。這樣雖然自由,但是卻並不安全。

Swift世界就不一樣了,Swift的世界很安全(至少大部分時候情況如此)。我們不必為物件執行時的型別擔憂,這是Swift為我們構築的一層堡壘。但是在一些時候,這層堡壘也成為束縛我們行為的操作。

正文

Swift也為操作指標這種不安全行為提供了支援。

MemoryLayout

MemoryLayout是Swift 為結構體struct等一些需要獲取具體記憶體空間大小定義的列舉。

它包含一系列方法,可以使我們更方便的使用。

主要屬性就是三個:

/// 對應型別的例項在記憶體佔用的真實位元組大小
public static var size: Int { get }
/// 對應型別的例項在記憶體經過對齊後佔用的位元組大小 (記憶體對齊請參照百度百科,連結將會在下文給出)
public static var stride: Int { get }
/// 預設記憶體對齊單位
public static var alignment: Int { get }
複製程式碼

記憶體對齊百度百科: baike.baidu.com/item/記憶體對齊/9…

這三個屬性也是我們經常使用的,對應還有三個從例項物件中獲取的方法

playgroud 介紹程式碼

struct Test {
    let res1: Bool = false
    let res2: Int = 0
    let res3: Bool = false
}

print("\(MemoryLayout<Test>.stride)") // 24
複製程式碼

因為這個類不是我們首要要介紹的,所以就只是大概介紹

記憶體指標

指標定義:

在電腦科學中,**指標(Pointer)**是程式語言中的一個物件,利用地址,它的值直接指向(points to)存在電腦儲存器中另一個地方的值。由於通過地址能找到所需的變數單元,可以說,地址指向該變數單元。因此,將地址形象化的稱為“指標”。意思是通過它能找到以它為地址的記憶體單元。

節選自百度百科: baike.baidu.com/item/指標/287…

Swift中認為所有的指標相關操作都是不安全的,所以所有指標相關的結構體都會包含有字首Unsafe

Swift 中一共有8個結構體

Swift Objective-c 描述
UnsafePointer<T> const T * 指標及其所指向的記憶體內容均不可變
UnsafeMutablePointer<T> T * 指標及其所指向的記憶體內容均可變
UnsafeBufferPointer<T> const T * [] 是一個指標陣列內容均不可變
UnsafeMutableBufferPointer<T> T * []
UnsafeRawPointer const void *
UnsafeMutableRawPointer void *
UnsafeBufferRawPointer void * []

首先要說一個例項類物件如何轉化為指標

關於Swift中的指標的那些事
UnsafePointer程式碼展示:

struct Obj {
    var name: Int = 5
}

var obj = Obj()

let pointer = withUnsafePointer(to: &obj, {$0})

print("\(pointer.pointee)")
複製程式碼

這裡注意了,UnsafePointer.pointee只有get方法,也就是相當於我們獲取了一個let的物件,遵從於let的相關要求。

注:這裡多介紹一下letvar的區別

letvar 都是swift宣告變數時候的字首。在程式中,當我們宣告變數的時候,在真實執行時候的環境裡,程式便會為我們宣告的變數初始化對應的記憶體。

let宣告的變數的記憶體內容,在宣告初始化過後便不再可變

var宣告的變數的記憶體內容,在隨後可以隨時被修改。

這裡我們使用的是記憶體內容,也就是抽象的記憶體地址裡的那些0、1的值。

對於classletvar的區別是這個變數能不能再被賦值一個新的類物件,而對原有物件的相關儲存屬性的修改,並不受影響。這是因為class的物件在宣告時候的堆疊記憶體內容,只有8個位元組(我們可以使用MemoryLayout獲取),真實物件的儲存在其他位置。所以我們能修改let指向的值

對於structletvar的區別就是let建立的物件,不論屬性是用let還是var,我們都無法直接修改值。只能修改var修飾的。這個是因為struct物件是直接儲存在宣告時候的記憶體內容裡。

對於UnsafeMutablePointer來說,pointee便是setget方法了,也就是說,我們可以直接修改對應記憶體地址的值了。

這裡展示下如何獲取UnsafeMutablePointer與修改相關值

struct Obj {
    var name: Int = 5

    init(name: Int = 5) {
        self.name = name
    }
}

var obj = Obj()

let pointer = withUnsafeMutablePointer(to: &obj, {$0})

print("Obj: \(obj)")

pointer.pointee = Obj(name: 10)

print("\(pointer.pointee)")

print("Obj: \(obj)")
複製程式碼

當執行這段程式碼的時候,我們可以看到,obj的內容也隨著指標變化了。

注:obj只能使用var宣告,因為Unsafe類的呼叫需要使用&,同時有可能產生記憶體內容的變化,因此let定義下不符合相關要求。

接下來是我要講的就是Swift中等同於C語言中void *的指標UnsafeRawPointer

關於Swift中的指標的那些事
有可能到這裡就會有疑問了,我們已經有了UnsafePointer了,這個UnsafeRawPointer究竟有什麼用呢?

在蘋果的官方文件中,關於UnsafeRawPointer的定義為

A raw pointer for accessing untyped data.

一個用來訪問未定義型別的指標

Overview

The UnsafeRawPointer type provides no automated memory management, no type safety, and no alignment guarantees. You are responsible for handling the life cycle of any memory you work with through unsafe pointers, to avoid leaks or undefined behavior.

UnsafeRawPointer 提供了沒有自動記憶體管理,沒有型別安全,與記憶體對齊控制保證。你有義務去自己管理你通過指標引用的任何記憶體的宣告週期,以避免記憶體洩漏或者其餘未定義(不安全)的行為

Memory that you manually manage can be either untyped or bound to a specific type. You use the UnsafeRawPointer type to access and manage raw bytes in memory, whether or not that memory has been bound to a specific type.

你手動管理的記憶體可以是未定義記憶體的,或者是被定義到一個指定的型別的。你可以使用UnsafeRawPointer去訪問和管理未被定義的記憶體位元組,而無需在意這段記憶體是否已經被繫結到一個指定的型別。

官方定義是這樣的,大致思路就是,如果你有需求需要自己管理記憶體的時候,使用這個類。一般就是我們操作底層的一些程式碼,這個會需要被當成引數傳入。

程式碼示例:

var i = 2

let pointerRaw = withUnsafeBytes(of: &i, {$0})

guard let pointer = pointerRaw.bindMemory(to: Int.self).baseAddress else { fatalError("這段程式碼理論上不能執行到這") }

let j = pointer.pointee

print("j:\(j)")
複製程式碼

以上程式碼就是轉換rawPointer和pointer的例子了。剩下的是bufferPointer,因為使用的地方較少了,故此不深入介紹了。

總結

指標相關中最重要的幾個操作就是指標的生成,與將指標轉化成例項物件。其中重要的就是pointee和幾個withUnsafeXX的全域性函式。

swift中,指標的世界並不複雜,所有指標的操作都能轉化為8個UnsafeXX類。我們的使用也是基於此的,我們如果使用某些底層方法需要指標,這個時候只需要將相應的指標物件生成即可傳入。

結尾

一轉眼已經元旦了,期間也想過寫一些文章,但是對於沒什麼東西要寫感到困惑。同時,這段時間也是一直在學習Swift的相關語言。因為個人專案中將要用到socket操作,於是決定用Swift重寫GCDAsyncSocket。在重寫過程中遇到了很多指標類似的問題,於是決定寫一篇關於指標的教程。

Swift的socket庫SwiftAsyncSocket目前正在完善中,TCP/IP協議基礎功能已基本完善。近期將會正式開源

本文首發於,本人部落格與公眾號(見下圖),如果希望轉載到公眾號,請聯絡本人開通許可權。

公眾號

最後,祝大家新年快樂,心想事成

相關文章