Swift5.0
穩定版就已經發布了, 感興趣的小夥伴可看個人這篇博客:Swift 5.0新特性更新Swift
的內存管理的相關介紹和剖析, 測試環境: Xcode 11.2.1
, Swift 5.1.2
OC
同樣, 在Swift
中也是採用基於引用計數的ARC
內存管理方案(針對堆空間的內存管理)Swift
的ARC
中有三種引用
strong reference
):默認狀況下,代碼中涉及到的引用都是強引用weak reference
):經過weak
定義弱引用unowned reference
):經過unowned
定義無主引用weak reference
):經過weak
定義弱引用
var
,由於實例銷燬後,ARC
會自動將弱引用設置爲nil
ARC
自動給弱引用設置nil
時,不會觸發屬性觀察weak
弱引用以前, 先看一下下面一段代碼class Animal { deinit { print("Animal deinit") } } func test() { let animal = Animal() } print("will deinit") test() print("did deinit") 複製代碼
上面這段代碼中在test
函數調用結束以後, 該做用的內存就會被回收, animal
對象天然就會被銷燬, 毫無疑問上面的輸出結果應該是html
will deinit Animal deinit did deinit 複製代碼
一樣下面這段代碼, 一樣也是在a1
對象被置爲nil
的時候內存會被回收, 對象就會被銷燬swift
var a1: Animal? = Animal() print("will deinit") a1 = nil print("did deinit") 複製代碼
weak
修飾的弱引用對象,weak
修飾的弱引用對象, 在對象銷燬的時候, 會被自動置爲nil
weak
修飾的弱引用對象必須是可選類型的var
, 兩個條件缺一不可weak var a2: Animal? = Animal() // 如下兩種方式都會報錯的 weak var a2: Animal = Animal() weak let a2: Animal? = Animal() 複製代碼
unowned reference
):經過unowned
定義無主引用unsafe_unretained
)Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocate 複製代碼
另外須要注意的是, weak
、unowned
只能用在類實例上面, 以下所示安全
// 該協議表示只能被類遵照, AnyObject表明全部的類實例 protocol Liveable: AnyObject {} class Person {} weak var p0: Person? weak var p1: AnyObject? // 全部能遵循Liveable協議的確定都是類 weak var p2: Liveable? unowned var p10: Person? unowned var p11: AnyObject? unowned var p12: Liveable? 複製代碼
weak
、unowned
都能解決循環引用的問題,unowned
要比weak
少一些性能消耗nil
的使用weak
nil
的使用unowne
閉包表達式默認會對用到的外層對象產生額外的強引用(對外層對象進行了retain
操做), 看一下下面的代碼中deinit
會被調用嗎?bash
class Person { var fn: (() -> ())? func run() { print("run") } deinit { print("deinit") } } func test() { let p = Person() p.fn = { p.run() } } test() 複製代碼
p
對象強引用着fn
閉包, fn
閉包也強引用着p
對象, 天然就形成了循環引用問題init
引用計數爲----1retain
引用計數會加(1), 結果爲----2release
引用計數會減(1), 結果爲----1p
對象確定沒有被釋放weak
或unowned
引用,用以解決循環引用問題// 使用weak func test() { let p = Person() p.fn = { [weak p] in p?.run() } } // 使用unowned func test() { let p = Person() p.fn = { [unowned p] in p.run() } } 複製代碼
Person
對象調用了deinit
另外下面這段代碼實際上是等價的微信
func test() { let p = Person() p.fn = { [unowned p] in p.run() } } // 和上面等價代碼 func test() { let p = Person() p.fn = { [unowned ownedP = p, weak weakP = p] in ownedP.run() // weakP?.run() } } 複製代碼
特別注意點, 這裏要區分捕獲列表和參數列表, 下面看看fn
有參數的狀況下markdown
class Person { var fn: ((Int) -> ())? func run() { print("run") } deinit { print("deinit") } } func test() { let p = Person() p.fn = { (num) in print("num = \(num)") } } 複製代碼
那麼閉包的參數列表和捕獲列表同事存在的狀況以下代碼所示閉包
func test() { let p = Person() p.fn = { [weak p](num) in print("num = \(num)") p?.run() } } 複製代碼
self
, 這個閉包必須是lazy
的self
class Person { lazy var fn: (() -> ()) = { self.run() } func run() { print("run") } deinit { print("deinit") } } func test() { let p = Person() p.fn() } test() 複製代碼
fn
閉包去掉lazy
, 編譯器會直接報錯Swift
中, 爲了保證初始化的安全, 設定了兩段式初始化, 在全部的存儲屬性被初始化完成以後, 初始化器纔可以使用self
fn
閉包中, 若是fn
內部用到了實例成員(屬性和方法), 則編譯器會強制要求明確寫出self
lazy
既保證只有在使用的時候纔會被初始化一次Person
對象強引用着fn
閉包, fn
閉包也強引用着self
weak
和unowned
解決循環引用的問題// weak解決循環引用 lazy var fn: (() -> ()) = { [weak self] in self?.run() } // unowned解決循環引用 lazy var fn: (() -> ()) = { [unowned self] in self.run() } 複製代碼
另外再看看下面這種狀況, 是都存在循環引用的問題函數
class Student { var age: Int = 2 lazy var getAge: Int = { self.age }() deinit { print("deinit") } } func test() { let p = Student() print(p.getAge) } test() /* 輸出結果 2 deinit */ 複製代碼
經過輸出結果看一看出調用了deinit
, 說明對象最終被釋放, 並未出現循環引用的問題, 下面比較一下oop
// 存在循環引用 class Person { lazy var fn: (() -> ()) = { self.run() } func run() { print("run") } deinit { print("deinit") } } // 不存在循環引用 class Student { var age: Int = 2 lazy var getAge: Int = { self.age }() deinit { print("deinit") } } 複製代碼
Person
對象中的fn
閉包屬於閉包賦值Student
對象那個中的getAge
屬於閉包調用(相似函數調用)Student
對象調用getAge
結束以後, 做用域內的變量就會被釋放// getAge也能夠寫成以下形式 lazy var getAge: Int = { return self.age }() // 也能夠理解爲 lazy var getAge: Int = self.age 複製代碼
在Swift
中的內存訪問衝突主要在兩個訪問知足下列條件時發生性能
// 不存在內存訪問衝突 var number = 1 func plus(_ num: inout Int) -> Int { return num + 1 } number = plus(&number) // 存在內存訪問衝突 var step = 1 func increment(_ num: inout Int) { num += step } increment(&step) 複製代碼
上面第二部分代碼就是同時對step
變量執行讀寫操做, 運行時會報出以下錯誤
Simultaneous accesses to 0x100002028, but modification requires exclusive access.
複製代碼
再看下面對於結構體和元組的使用, 這裏先定義一個全局函數和一個結構體
// 改變兩個傳入參數的值, 讀取並修改傳入參數的值 func balance(_ x: inout Int, _ y: inout Int) { let sum = x + y x = sum / 2 y = sum - x } // 定義Player結構體 struct Player { var name: String var health: Int var energy: Int mutating func shareHealth(with teammate: inout Player) { balance(&teammate.health, &health) } } 複製代碼
再看下面的使用示例, 二者都會有一個內存訪問衝突的錯誤
// 這裏讀寫的是同一個maria var maria = Player(name: "Maria", health: 50, energy: 10) balance(&maria.health, &maria.energy) // 這裏讀寫的是同一個tuple var tuple = (health: 10, energy: 20) balance(&tuple.health, &tuple.energy) 複製代碼
可是有時候的確會有上面這種訪問同一塊內存的需求, 若是下面的條件知足, 就說明重疊訪問結構體的屬性是安全的
// 這裏能夠在局部做用域內定義成局部變量, 就不會有問題了 func test() { var maria = Player(name: "Maria", health: 50, energy: 10) var tuple = (health: 10, energy: 20) balance(&tuple.health, &tuple.energy) balance(&maria.health, &maria.energy) } 複製代碼
class Person {} var person = Person() 複製代碼
Swift
中class
聲明的類(Person
)是引用類型, 初始化的person
對象其本質上就是一個指針變量person
裏面存儲的就是這個指針變量的地址值, 也就能夠根據這個地址值去訪問被分配的內存空間在Swift
中也有專門的指針類型,這些都被定性爲Unsafe
(不安全的),常見的有如下4種類型
UnsafePointer<Pointee>
, 相似於C語言中的const Pointee *
, 只能訪問內存不能修改內存, 這裏的Pointee
是指泛型UnsafeMutablePointer<Pointee>
相似於C語言中的Pointee *
, 能夠訪問和修改內存, 這裏的Pointee
是指泛型UnsafeRawPointer
相似於const void *
, 不支持泛型UnsafeMutableRawPointer
相似於void
, 不支持泛型下面看一下具體的使用示例
var age = 10 func sum1(_ ptr: UnsafeMutablePointer<Int>) { // 經過訪問pointee屬性, 獲取ptr指針的內存地址所存儲的值 // UnsafeMutablePointer的pointee屬性是可讀可寫的 ptr.pointee += 10 } func sum2(_ ptr: UnsafePointer<Int>) { // UnsafePointer的pointee屬性是隻讀的 // ptr.pointee += 10 print(ptr.pointee) } func sum3(_ num: inout Int) { // num += 10 } // 和inout輸入輸出參數同樣接受變量的地址值 sum1(&age) sum2(&age) sum3(&age) print(age) func sum4(_ ptr: UnsafeMutableRawPointer) { // 可讀可寫, 取值 print("age = ", ptr.load(as: Int.self)) // 可讀可寫, 賦值 ptr.storeBytes(of: 50, as: Int.self) } func sum5(_ ptr: UnsafeRawPointer) { // 只讀, 取值 print("age = ", ptr.load(as: Int.self)) } sum4(&age) sum5(&age) 複製代碼
Swift
中有能夠直接獲取變量的指針的方法
// 獲取可變的變量指針, value參數接受變量地址 @inlinable public func withUnsafeMutablePointer<T, Result>(to value: inout T, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result // 獲取不可變的變量指針, value參數接受變量 @inlinable public func withUnsafePointer<T, Result>(to value: T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result // 獲取不可變的變量指針, value參數接受變量地址 @inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result 複製代碼
上述方法中返回值默認是變量的指針地址, 也能夠是其餘的數據類型, 主要取決於body
閉包的返回值, 返回值類型由閉包中的Result
泛型決定
var age = 10 var ptr1 = withUnsafeMutablePointer(to: &age) { $0 } // UnsafeMutablePointer<Int> var ptr2 = withUnsafePointer(to: &age) { $0 } // UnsafePointer<Int> ptr1.pointee = 22 print(ptr2.pointee) // 22 print(ptr2) // 0x0000000100008310 var ptr3 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0) } // UnsafeMutableRawPointer var ptr4 = withUnsafePointer(to: &age) { UnsafeRawPointer($0) } // UnsafeRawPointer // as參數是須要存儲什麼類型的數據 ptr3.storeBytes(of: 33, as: Int.self) print(ptr4.load(as: Int.self)) // 33 print(ptr4) // 0x0000000100008310 複製代碼
Swift
提供了malloc
直接分配內存建立指針的方式
// 根據須要分配的內存大小建立一個指針 public func malloc(_ __size: Int) -> UnsafeMutableRawPointer! // 釋放內存 public func free(_: UnsafeMutableRawPointer!) // 下面這兩個函數, 是賦值和取值的函數, 以前簡單介紹過 // 參數一: 須要存儲的值 // 參數二: 偏移量, 從第幾個字節開始存儲, 默認從第一個 // 參數三: 須要存儲的值的類型 @inlinable public func storeBytes<T>(of value: T, toByteOffset offset: Int = 0, as: T.Type) // 參數一: 偏移量, 從第幾個字節開始存儲, 默認從第一個 // 參數二: 須要存儲的值的類型 @inlinable public func load<T>(fromByteOffset offset: Int = 0, as type: T.Type) -> T 複製代碼
代碼示例以下
// 建立指針 var ptr = malloc(16) // 存儲值 ptr?.storeBytes(of: 10, as: Int.self) // 這裏toByteOffset參數若是傳0, 就會覆蓋前8個字節的數據 ptr?.storeBytes(of: 12, toByteOffset: 8, as: Int.self) // 取值 print(ptr?.load(as: Int.self) ?? 0) print(ptr?.load(fromByteOffset: 8, as: Int.self) ?? 0) // 銷燬, 釋放內存 free(ptr) 複製代碼
使用allocate
方式建立指針, 代碼示例以下
// byteCount: 須要申請的字節數, alignment: 對其字節數 var ptr2 = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1) // 存儲 ptr2.storeBytes(of: 9, as: Int.self) // 根據字節偏移存儲 // 這裏的ptr3是ptr2偏移8個字節的新的指針地址 var ptr3 = ptr2.advanced(by: 8) // UnsafeMutableRawPointer ptr3.storeBytes(of: 12, as: Int.self) // 上面這種方式等價於 ptr2.storeBytes(of: 12, toByteOffset: 8, as: Int.self) // 取值一樣 print(ptr2.load(as: Int.self)) // 下面這兩種取值方式也是同樣的 print(ptr2.advanced(by: 8).load(as: Int.self)) print(ptr2.load(fromByteOffset: 8, as: Int.self)) // 釋放內存 ptr2.deallocate() 複製代碼
這裏須要注意的是隻有UnsafeMutableRawPointer
纔有allocate
分配方法, UnsafeRawPointer
是沒有這個方法的, 下面說到的UnsafeMutablePointer<T>
類型也是, UnsafePointer<T>
沒有allocate
分配方法
// capacity: 容量, 便可以存儲3個Int類型的數據, 也就是24個字節 var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 3) // 初始化內存, 用10初始化錢8個字節 ptr.initialize(to: 10) // 用10初始化前兩個容量的內存, 即16個字節 ptr.initialize(repeating: 10, count: 2) // 使用successor獲取下一個存儲位, 也就是下一個Int的位置 var ptr1 = ptr.successor() // UnsafeMutablePointer<Int> ptr1.initialize(to: 20) // 存儲第三個Int值 ptr.successor().successor().initialize(to: 30) // 取值的兩種方式 print(ptr.pointee) // 第一個值 print((ptr + 1).pointee) // 第二個值 print((ptr + 2).pointee) // 第三個值 // 下面這種方式和上面等價 print(ptr[0]) print(ptr[1]) print(ptr[2]) // 前面若是使用了initialize, 則必須調用反初始化 // 並且count要和上面allocate(capacity: 3)的capacity一致, 不然會形成內存泄露的問題 ptr.deinitialize(count: 3) ptr.deallocate() 複製代碼
前面提到過Swift
中的指針類型有四種
UnsafePointer<Pointee>
相似於const Pointee *
UnsafeMutablePointer<Pointee>
相似於Pointee *
UnsafeRawPointer
相似於const void *
UnsafeMutableRawPointer
相似於void *
UnsafeMutableRawPointer
中有一個初始化方法能夠根據UnsafeMutablePointer
建立自身
public init<T>(_ other: UnsafeMutablePointer<T>) var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 3) var ptr1 = UnsafeMutableRawPointer(ptr) 複製代碼
反過來, UnsafeMutableRawPointer
也提供了一個方法用於建立UnsafePointer
public func assumingMemoryBound<T>(to: T.Type) -> UnsafePointer<T> var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1) var ptr1 = ptr.assumingMemoryBound(to: Int.self) // 初始化前8個字節 ptr1.pointee = 11 // 初始化後8個字節 // 特別注意, 這裏的(ptr + 8)是指ptr向後偏移8個字節, 要和以前的區分開 (ptr + 8).assumingMemoryBound(to: Int.self).pointee = 12 ptr.deallocate() 複製代碼
unsafeBitCast
是忽略數據類型的強制轉換,不會由於數據類型的變化而改變原來的內存數
// 把第一個參數類型轉成第二個參數類型 @inlinable public func unsafeBitCast<T, U>(_ x: T, to type: U.Type) -> U var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1) unsafeBitCast(ptr, to: UnsafeMutablePointer<Int>.self).pointee = 13 // 注意, 這裏的(ptr + 8)是指ptr向後偏移8個字節, 要和以前的區分開 unsafeBitCast(ptr + 8, to: UnsafeMutablePointer<Double>.self).pointee = 14.23 ptr.deallocate() 複製代碼
歡迎您掃一掃下面的微信公衆號,訂閱個人博客!