大部分狀況下作Swift開發是不須要使用指針的,也不建議使用,可是有時候寫比較底層的東西就須要了。最近一段時間剛好我在寫的一些庫的須要用到指針,可是Swift關於指針的使用並無很詳細的文檔,致使寫起代碼來十分費勁,因此總結了一下。Runtime的文章不少,可是Swift的不多,因此我準備了Swift三部曲,介紹底層相關知識,有些以前看過了解過,但談不上很深刻,因此會邊寫邊研究,分別是:html
Swift三部曲(一):指針的使用
Swift三部曲(二):內存佈局
Swift三部曲(三):方法派發git
第一篇就是本文,第二篇和第三篇還沒寫,不過最近會陸續寫完。github
// 單位均爲字節
MemoryLayout<T>.size // 類型T須要的內存大小
MemoryLayout<T>.stride // 類型T實際分配的內存大小(因爲內存對齊原則,會多出空白的空間)
MemoryLayout<T>.alignment // 內存對齊的基數
複製代碼
C 和 Swift關於指針的對照表:swift
C | Swift | 註解 |
---|---|---|
const Type * |
UnsafePointer<Type> |
指針可變,指針指向的內存值不可變 |
Type * |
UnsafeMutablePointer<Type> |
指針和指針指向的內存值都可變 |
ClassType * const * |
UnsafePointer<UnsafePointer<Type>> |
指針的指針:指針不可變,指針指向的類可變 |
ClassType ** |
UnsafeMutablePointer<UnsafeMutablePointer<Type>> |
指針的指針:指針和指針指向的類都可變 |
ClassType ** |
AutoreleasingUnsafeMutablePointer<Type> |
做爲OC方法中的指針參數 |
const void * |
UnsafeRawPointer |
指針指向的內存區,類型未定 |
void * |
UnsafeMutableRawPointer |
指針指向的內存區,類型未定 |
StructType * |
OpaquePointer |
c語言中的一些自定義類型,Swift中並未有相對應的類型 |
int a[] |
UnsafeBufferPointer/UnsafeMutableBufferPointer |
一種數組指針 |
Swift中的指針分爲兩大類, typed pointer 指定數據類型指針, raw pointer 未指定數據類型的指針(原生指針)。數組
typed pointer |
---|
UnsafePointer |
UnsafeMutablePointer |
UnsafeBufferPointer |
UnsafeMutableBufferPointer |
被UnsafeMutablePointe引用的內存有三種狀態:安全
allocatebash
// 綁定類型並分配內存
// allocate是類方法
// capacity: Int表示向系統申請 capacity 個數的對應泛型類型的內存
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: MemoryLayout<Int>.stride)
// let pointer = UnsafeMutablePointer<CCInfo>.allocate(capacity: MemoryLayout.stride(ofValue: CCInfo()))
複製代碼
initialize閉包
// 初始化, 對於Int, Float, Double這些基本數據類型,分配內存以後會有默認值0
pointer.initialize(to: 12)
// pointer.pointee 爲 12
// 賦值
pointer.pointee = 10
複製代碼
deinitializeapp
// 與 initialize: 配對使用的 deinitialize: 用來銷燬指針指向的對象
// 回到初始化值以前,沒有釋放指針指向的內存,指針依舊指向以前的值
pointer.deinitialize(count: 1)
複製代碼
deallocateasync
// 與 allocate(capacity:) 對應的 deallocate() 用來釋放以前申請的內存
pointer.deallocate()
複製代碼
注意其實在這裏對於 Int 這樣的在 C 中映射爲 int 的 「平凡值」 來講,deinitialize 並非必要的,由於這些值被分配在常量段上。可是對於像類的對象或者結構體實例來講,若是不保證初始化和摧毀配對的話,是會出現內存泄露的。因此沒有特殊考慮的話,不論內存中究竟是什麼,保證 initialize: 和 deinitialize 配對會是一個好習慣。
UnsafePointer 是不可變的,C 中 const 修飾的指針對應 UnsafePointer (最多見的應該就是 C 字符串的 const char * 了)。
初始化
能夠由UnsafeMutablePointer、OpaquePointer或其餘UnsafePointer建立一個UnsafePointer指針。其餘與UnsafeMutablePointer相似。
//經過另外一個變量指針初始化一個`UnsafePointer`常量指針
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: MemoryLayout<Int>.stride)
pointer.pointee = 20
let pointer1 = UnsafePointer<Int>.init(pointer)
print(pointer1.pointee) // 20
複製代碼
withMemoryRebound
將內存臨時從新綁定到其餘類型。
var int8: Int8 = 123
let int8Pointer = UnsafeMutablePointer(&int8)
int8Pointer.withMemoryRebound(to: Int.self, capacity: 8) { ptr in
print(ptr.pointee) // 123
}
複製代碼
bindMemory
該方法綁定內存爲指定類型並返回一個UnsafeMutablePointer<指定類型>的指針,用到了指向內存的原始指針。
let intPointer = UnsafeRawPointer(int8Pointer).bindMemory(to: Int.self, capacity: 1)
print(intPointer.pointee) // 123
複製代碼
在使用 bindMemory方法將原生指針綁定內存類型,轉爲類型指針的時候,一次只能綁定一個類型,例如:將一個原生指針綁定Int類型,不能再綁定Bool類型。
assumingMemoryBound
該方法意思是直接轉換這個原始指針爲一個UnsafeMutablePointer<指定類型>的指針。
let strPtr = UnsafeMutablePointer<CFString>.allocate(capacity: 1)
let rawPtr = UnsafeRawPointer(strPtr)
let intPtr = rawPtr.assumingMemoryBound(to: Int.self)
複製代碼
UnsafeBufferPointer表示一組連續數據指針。BufferPointer實現了Collection,所以能夠直接使用Collection中的各類方法來遍歷操做數據,filter,map...,Buffer能夠實現對一塊連續存在空間進行操做,相似C中的數組的指針。 可是一樣的,這個UnsafeBufferPointer是常量,它只能獲取到數據,不能經過這個指針去修改數據。與之對應的是UnsafeMutableBufferPointer指針。
var array = [1, 2, 3, 4]
// 遍歷
let ptr = UnsafeBufferPointer.init(start: &array, count: array.count)
ptr.forEach { element in
print(element) // 1,2,3,4
}
//遍歷
array.withUnsafeBufferPointer { ptr in
ptr.forEach {
print($0) // 1,2,3,4
}
}
複製代碼
UnsafeBufferPointer 可使用 baseAddress 屬性,這個屬性包含了緩衝區的基本地址。
let array: [Int8] = [65, 66, 67, 0]
puts(array) // ABC
array.withUnsafeBufferPointer { (ptr: UnsafeBufferPointer<Int8>) in
puts(ptr.baseAddress! + 1) //BC
}
複製代碼
let count = 2
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
pointer.initialize(repeating: 0, count: count)
defer {
pointer.deinitialize(count: count)
pointer.deallocate()
}
pointer.pointee = 42 // pointer 指向的內存地址存放數值 42
pointer.advanced(by: 1).pointee = 6 // pointer 下一個內存地址存放數值 6,即 pointer 指向的起始地址加 Int 類型的步幅再移動 1 位,就其起始地址
pointer.pointee
pointer.pointee
pointer.advanced(by: 1).pointee
let bufferPointer = UnsafeBufferPointer(start: pointer, count: count)
for (index, value) in bufferPointer.enumerated() {
print("value \(index): \(value)") // value 0: 42, value 1: 6
}
複製代碼
可變的序列指針,UnsafeMutableBufferPointer擁有對指向序列修改的能力:
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
let bufferPointer = UnsafeMutableBufferPointer<Int>.init(start: pointer, count: 5) // 拓展爲5各元素的大小
bufferPointer[0] = 120
bufferPointer[1] = 130 //進行修改,其餘未修改的內容將產生隨機值
bufferPointer.forEach { (a) in
print(a) // 120, 130, 120054000649232, 73, 105553129173888
}
print(bufferPointer.count) // 5
複製代碼
情況跟UnsafeBufferPointer有點相似,只是在初始化的時候,須要藉助UnsafeMutablePointer。 並不能直接使用已經存在序列進行初始化。 值的注意的是:若是一個序列被初始化以後,沒有給每個元素賦值的話,這些元素的值都將出現隨機值
raw pointer |
---|
UnsafeRawPointer |
UnsafeMutableRawPointer |
UnsafeRawBufferPointer |
UnsafeMutableRawBufferPointer |
UnsafeMutableRawPointer 用於訪問和操做非類型化數據的原始指針。
// 分配內存, byteCount: 表示總共須要的字節數, 表示 Int 類型的對齊方式
let pointer = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: MemoryLayout<Int>.alignment)
// 將給定值存儲在指定偏移量的原始內存中
pointer.storeBytes(of: 0x00060001, as: UInt32.self)
// 從pointer引用的內存 用UInt8實例加載(即第一個字節用UInt8實例加載)
let value = pointer.load(as: UInt8.self)
print(value) // 1
// pointer.storeBytes(of: 42, as: Int.self)
// let value = pointer.load(as: Int.self)
// print(value) 42
let offsetPointer = pointer.advanced(by: 2)
// let offsetPoint = pointer + 2 // 偏移 2個字節, 若是偏移3個字節,下面的操做就會越界了
let offsetValue = offsetPointer.load(as: UInt16.self) // 將第三個和第四個字節做爲UInt16實例加載
print(offsetValue) // 6
pointer.deallocate()
複製代碼
注:方法 storeBytes 和 load 分別是用來存儲和讀取字節數的。
用於訪問非類型化數據的原始指針。UnsafeRawPointer只能由其餘指針用init方法獲得,與UnsafePointer相似,沒有allocate靜態方法。可是,與UnsafeMutableRawPointer相似的有兩種綁定方法bindMemory和assumingMemoryBound,綁定成UnsafePointer指針。
// 訪問不一樣類型的相同內存
var uint64: UInt64 = 257
let rawPointer = UnsafeRawPointer(UnsafeMutablePointer(&uint64))
let int64PointerT = rawPointer.load(as: Int64.self)
let uint8Point = rawPointer.load(as: UInt8.self)
print(int64PointerT) // 257
print(uint8Point) // 1
// 257 = 1 0000 0001 而UInt8 表示存儲8個位的無符號整數,即一個字節大小, 2^8 = 256, [0, 255], 超出8個位範圍的沒法加載,因此打印爲1
複製代碼
引用Swift內存賦值探索二: 指針在Swift中的使用的描述:
UnsafeRawBufferPointer和UnsafeMutableRawBufferPointer 指代的是一系列的沒有被綁定類型的內存區域。咱們能夠理解成他們實際上就是一些數組,再綁定內存以前,其中包含的元素則是每個字節。 在底層,基本數據單元的複製是有效的,另外沒有被 retain 和 stong 的也是可以安全的複製的,一樣的,對於來自C API的對象也可以安全的複製。對於原聲的Swift類型,有的包含了引用的對象的複製則有可能失敗,可是咱們可使用指針對他們的值進行復制,這樣的結果是有效的。若是咱們強行對一下發類型進行復制,不必定有效,除非使用像C語言中的APImemmove().來操做
UnsafeRawBufferPointer和UnsafeMutableRawBufferPointer是內存視圖,儘管咱們知道它指向的內存區域,可是它並不擁有這塊內存的引用。複製UnsafeRawBufferPointer 類型的變量不會複製它的內存;可是初始化一個集合到另外一個新的集合過程會複製集合中的引用內存。
總結:
let pointer = UnsafeMutableRawBufferPointer.allocate(byteCount: 3, alignment: MemoryLayout<Int>.alignment)
pointer.copyBytes(from: [1, 2, 3])
pointer.forEach {
print($0) // 1, 2, 3
}
複製代碼
要經過類型化操做訪問底層內存,必須將內存綁定到一個簡單的類型。
typed pointer |
---|
withUnsafePointer |
withUnsafeMutablePointers |
withUnsafeBytes |
withUnsafeMutableBytes |
Swift 中不能像 C 裏那樣使用 & 符號直接獲取地址來進行操做。若是咱們想對某個變量進行指針操做,咱們能夠藉助 withUnsafePointer 或 withUnsafeMutablePointer 這兩個輔助方法。withUnsafePointer 或 withUnsafeMutablePointer 的差異是前者轉化後的指針不可變,後者轉化後的指針可變。
基本數據類型
var a = 0
withUnsafePointer(to: &a) { ptr in
print(ptr) // 0x00007ffeeccb3b40
}
a = withUnsafePointer(to: &a) { ptr in
return ptr.pointee + 2
// 此時, 會新開闢空間, 令a指向新地址, 值爲2,
}
// 修改指針指向的內存值
var b = 42
withUnsafeMutablePointer(to: &b) { ptr in
ptr.pointee += 100 // 未開闢新的內存空間, 直接修改a所指向的內存值
}
print(b) // 142
var arr = [1, 2, 3]
withUnsafeMutablePointer(to: &arr) { ptr in
ptr.pointee[0] = 10
}
print(arr) // [10, 2, 3]
arr.withUnsafeBufferPointer { ptr in
ptr.forEach{
print("\($0)") // 10 2 3
}
}
// 修改內存值
arr.withUnsafeMutableBufferPointer { ptr in
ptr[0] = 100
ptr.forEach {
print("\($0)") // 100 2 3
}
}
複製代碼
獲取 struct 類型實例的指針
struct User {
var name: Int = 5
init(name: Int = 5) {
self.name = name
}
}
var user = User()
let pointer = withUnsafeMutablePointer(to: &user, {$0})
print(user) // user
pointer.pointee = User(name: 10)
print("\(pointer.pointee)") // User(name: 10)
print(user) // User(name: 10)
複製代碼
獲取 class 類型實例的指針
獲取 class 類型實例的指針和上面不一樣,不是使用withUnsafePointer 或 withUnsafeMutablePointer,而是使用下面講到的Unmanaged,之因此放在這裏,是想由於這裏講到獲取對象指針,因此附帶講一下。
func headPointerOfClass() -> UnsafeMutablePointer<Int8> {
let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()
let mutableTypedPointer = opaquePointer.bindMemory(to: Int8.self, capacity: MemoryLayout<Self>.stride)
return UnsafeMutablePointer<Int8>(mutableTypedPointer)
}
複製代碼
可使用withUnsafeBytes/withUnsafeMutableBytes獲取實例的字節數。
// 打印字符串
let string = "hello"
let data = string.data(using: .ascii)
data?.withUnsafeBytes{ (ptr: (UnsafePointer<Int8>)) in
print(ptr.pointee) // 104 = 'h'
print(ptr.advanced(by: 1).pointee) // 101 = 'e'
}
// 打印結構體
struct SampleStruct {
let number: UInt32
let flag: Bool
}
MemoryLayout<SampleStruct>.size // returns 5
MemoryLayout<SampleStruct>.alignment // returns 4
MemoryLayout<SampleStruct>.stride // returns 8
var sampleStruct = SampleStruct(number: 25, flag: true)
withUnsafeBytes(of: &sampleStruct) { bytes in
for byte in bytes {
print(byte) // 25 0 0 0 1
}
}
let bytes = withUnsafeBytes(of: &sampleStruct) { bytes in
return bytes // 這裏會有奇怪的bug!
}
print("Horse is out of the barn!", bytes) // undefined !!!
複製代碼
注:
若是直接使用指針,那麼就須要咱們手動管理內存,這個並很差辦,因此蘋果引入了Unmanaged來管理引用計數,Unmanaged 可以將由 C API 傳遞過來的指針進行託管,咱們能夠經過Unmanaged標定它是否接受引用計數的分配,以便實現相似自動釋放的效果;同時,若是不是使用引用計數,也可使用Unmanaged 提供的release函數來手動釋放,這比在指針中進行這些操做要簡單不少。關於Unmanaged swifter.tips這篇TOLL-FREE BRIDGING 和 UNMANAGED文章好像不少地方都講錯了。
一個 Unmanaged 實例封裝有一個 CoreFoundation 類型 T,它在相應範圍內持有對該 T 對象的引用。
將一個對象聲明爲非託管方法有兩種:
從一個 Unmanaged 實例中獲取一個 Swift 值的方法有兩種:
這看起來仍是不知道什麼時候使用passRetained和passUnretained,什麼時候使用takeRetainedValue和takeUnretainedValue,蘋果提出了Ownership Policy:
let bestFriendID = ABRecordID(...)
// Create Rule - retained
let addressBook: ABAddressBook = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()
if let
// Get Rule - unretained
bestFriendRecord: ABRecord = ABAddressBookGetPersonWithRecordID(addressBook, bestFriendID)?.takeUnretainedValue(),
// Create Rule (Copy) - retained
name = ABRecordCopyCompositeName(bestFriendRecord)?.takeRetainedValue() as? String
{
println("\(name): BFF!")
// Rhonda Shorsheimer: BFF!
}
複製代碼
這些函數能夠經過函數名知道該怎麼使用Unmanaged,但不少時候在使用的不是這種命名的C函數,
Alamofire的NetworkReachabilityManager.swift中就有一段調用C方法使用了Unmanaged。
@discardableResult
open func startListening() -> Bool {
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = Unmanaged.passUnretained(self).toOpaque()
let callbackEnabled = SCNetworkReachabilitySetCallback(
reachability,
{ (_, flags, info) in
let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue()
reachability.notifyListener(flags)
},
&context
)
let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)
listenerQueue.async {
self.previousFlags = SCNetworkReachabilityFlags()
self.notifyListener(self.flags ?? SCNetworkReachabilityFlags())
}
return callbackEnabled && queueEnabled
}
複製代碼
由於self
對象的使用是在當前做用域內,也就是startListening方法內部,咱們能保證使用的時候對象一直存活,因此使用的passUnretained和takeUnretainedValue。
class Person {
func eat() {
print(#file, #line, "eat now")
}
}
func callbackFunc(userPtr: UnsafeMutableRawPointer?) {
if userPtr == nil { return }
let user = Unmanaged<Person>.fromOpaque(userPtr!).takeRetainedValue()
user.eat()
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var user = Person()
let userPtr = Unmanaged<Person>.passRetained(user).toOpaque()
callback(callbackFunc, userPtr)
}
}
複製代碼
咱們看到因爲使用了閉包,user對象的使用超出了當前做用域也就是viewDidLoad方法,因此須要使用takeRetainedValue和passRetained。
let userPtr = Unmanaged<Person>.passRetained(user).toOpaque()
複製代碼
使用passRetained()會建立一個被retained的指向這個對象的指針,這樣能夠保證在C中被調用的時候這個對象還在那,不會被銷燬,這個方法會產生一個Unmanaged的實例變量,而後經過toOpaque() 方法轉換爲 UnsafeMutableRawPointer。
let user = Unmanaged<Person>.fromOpaque(userPtr!).takeRetainedValue()
複製代碼
利用Unmanaged相反的方法,取出user對象,這種方法更加安全,能夠保證對象在傳遞過程當中一直存在,而且直接得到對象。
非託管對象使用週期超過了編譯器認爲的生命週期,好比超出做用域,必須手動 retain 這個對象,也就是使用 passRetained 方法。一旦你手動 retain 了一個對象,就不要忘記 release 掉它,方法就是調用非託管對象的 release 方法,或者用 takeRetainedValue 取出封裝的對象,並將其管理權交回 ARC。但注意,必定不要對一個用 passUnretained 構造的非託管對象調用 release 或者 takeRetainedValue,這會致使原來的對象被 release 掉,從而引起異常。
unmanaged仍是有點難的,我在其餘地方看到這段代碼,你們能夠在Playground試一試,若是有知道全部答案的,能夠留言討論一下。
class SomeClass {
let text: Int
init(text: Int) {
self.text = text
}
deinit {
print("Deinit \(text)")
}
}
do {
let unmanaged = Unmanaged.passRetained(SomeClass(text: 0))
unmanaged.release()
}
do {
let _ = Unmanaged.passUnretained(SomeClass(text: 1))
}
do {
let unmanaged = Unmanaged.passRetained(SomeClass(text: 2))
let _ = unmanaged.retain()
unmanaged.release()
Unmanaged<SomeClass>.fromOpaque(unmanaged.toOpaque()).release()
unmanaged.release()
}
do {
let opaque = Unmanaged.passRetained(SomeClass(text: 3)).toOpaque()
Unmanaged<SomeClass>.fromOpaque(opaque).release()
}
do {
let unmanaged = Unmanaged.passRetained(SomeClass(text: 4))
let _ = unmanaged.takeUnretainedValue()
unmanaged.release()
}
do {
let unmanaged = Unmanaged.passRetained(SomeClass(text: 5))
let _ = unmanaged.takeRetainedValue()
}
複製代碼
在C中有回調函數,當swift要調用C中這類函數時,可使用函數指針。Swift中能夠用@convention 修飾一個閉包
類型 | 註解 |
---|---|
@convention(swift) |
代表這個是一個swift的閉包 |
@convention(block) |
代表這個是一個兼容oc的block的閉包,能夠傳入OC的方法 |
@convention(c) |
代表這個是兼容c的函數指針的閉包,能夠傳入C的方法 |
第二個類型是這三個當中可能最經常使用的,當你在Swift使用Aspects中會用到,我用Swift寫的Aspect使用的時候也能夠這樣:
let wrappedBlock: @convention(block) (AspectInfo, Int, String) -> Void = { aspectInfo, id, name in
}
let block: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self)
test.hook(selector: #selector(Test.test(id:name:)), strategy: .before, block: )
複製代碼
咱們能夠看看源碼Builtin.swift
/// - Parameters:
/// - x: The instance to cast to `type`.
/// - type: The type to cast `x` to. `type` and the type of `x` must have the
/// same size of memory representation and compatible memory layout.
/// - Returns: A new instance of type `U`, cast from `x`.
@inlinable // unsafe-performance
@_transparent
public func unsafeBitCast<T, U>(_ x: T, to type: U.Type) -> U {
_precondition(MemoryLayout<T>.size == MemoryLayout<U>.size,
"Can't unsafeBitCast between types of different sizes")
return Builtin.reinterpretCast(x)
}
複製代碼
unsafeBitCast 是很是危險的操做,它會將一個指針指向的內存強制按位轉換爲目標的類型,而且只進行了簡單的 size 判斷。由於這種轉換是在 Swift 的類型管理以外進行的,所以編譯器沒法確保獲得的類型是否確實正確,你必須明確地知道你在作什麼。
let block: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self)
複製代碼
這段代碼就是將oc的block的閉包轉成Anyobject類型。
參考文章:
Unsafe Swift: Using Pointers And Interacting With C
UnsafeRawPointer Migration
Swift內存賦值探索二: 指針在Swift中的使用
Unmanaged
在Swift 3.0調用C語言API
Swift 中的指針使用
深度探究HandyJSON(一) Swift 指針的使用