Swift指針|內(nèi)存管理

一五鲫、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)存
  • BuiltinSwift的標(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é)如下:

  1. Swift中的指針分為兩種:
  • raw pointer:未指定類型(原生)指針灾常,即:UnsafeRawPointerunsafeMutableRawPointer
  • type pointer:指定類型的指針霎冯,即:UnsafePointer<T>UnsafeMutablePointer<T>
  1. 指針的綁定主要有三種方式:
  • withMemoryRebound:臨時更改內(nèi)存綁定類型
  • bingMemory(to: capacity:):更改內(nèi)存綁定的類型,如果之前沒有綁定钞瀑,那么就是首次綁定沈撞,如果綁定過了,會被重新綁定為該類型
  • assumingMemoryBound:假定內(nèi)存綁定雕什,這里就是告訴編譯器缠俺,我就是這種類型,不要檢查了(控制權(quán)由我們決定)
  1. 內(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鞋拟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惹资,更是在濱河造成了極大的恐慌贺纲,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褪测,死亡現(xiàn)場離奇詭異猴誊,居然都是意外死亡,警方通過查閱死者的電腦和手機侮措,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門懈叹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人分扎,你說我怎么就攤上這事澄成。” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵墨状,是天一觀的道長卫漫。 經(jīng)常有香客問我,道長肾砂,這世上最難降的妖魔是什么列赎? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮镐确,結(jié)果婚禮上粥谬,老公的妹妹穿的比我還像新娘。我一直安慰自己辫塌,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布派哲。 她就那樣靜靜地躺著臼氨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芭届。 梳的紋絲不亂的頭發(fā)上储矩,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天,我揣著相機與錄音褂乍,去河邊找鬼持隧。 笑死,一個胖子當(dāng)著我的面吹牛逃片,可吹牛的內(nèi)容都是我干的屡拨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼褥实,長吁一口氣:“原來是場噩夢啊……” “哼呀狼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起损离,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤哥艇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后僻澎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體貌踏,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年窟勃,在試婚紗的時候發(fā)現(xiàn)自己被綠了祖乳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡拳恋,死狀恐怖凡资,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤隙赁,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布垦藏,位于F島的核電站,受9級特大地震影響伞访,放射性物質(zhì)發(fā)生泄漏掂骏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一厚掷、第九天 我趴在偏房一處隱蔽的房頂上張望弟灼。 院中可真熱鬧,春花似錦冒黑、人聲如沸田绑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掩驱。三九已至,卻和暖如春冬竟,著一層夾襖步出監(jiān)牢的瞬間欧穴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人系吩。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像调缨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子苟鸯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容