參考文獻
Swift結構體指針操作
官方文檔
Swift 和 C 不得不說的故事
Swift指針和托管,你看我就夠了
Why?
swift中并不推薦對指針直接操作,但畢竟iOS中有的庫還是使用C語言構建的,在與C混編的時候,不可避免的要將對象轉換為C的指針類型以便傳值。比如GCD,CF框架Runloop操作。
各種指針
對應表
下面表中Type為類型占位,可替換為int等等
對于返回值、變量、參數的指針:
C | Swift | example |
---|---|---|
const Type * | UnsafePointer<Type> | const int * 轉換為UnsafePointer <Int32> |
Type * | UnsafeMutablePointer<Type> | int * 轉換為UnsafeMutablePointer <Int32> |
對于類對象的指針
C | Swift |
---|---|
Type * const * | UnsafePointer<Type> |
Type * __strong * | UnsafeMutablePointer<Type> |
Type ** | AutoreleasingUnsafeMutablePointer<Type> |
對于無符號類型、
C | Swift |
---|---|
const void * | UnsafeRawPointer |
void * | UnsafeMutableRawPointer |
如果像不完整結構體的這樣的c指針的值的類型無法用Swift來表示,則用OpaquePointer來表示
常量指針
定義為 UnsafePointer<Type>
的指針為常量指針,在C中有const修飾。
當函數的參數修飾為 UnsafePointer<Type>
的時候,可以接收下面幾種類型的值:
- 類型為
UnsafePointer<Type>, UnsafeMutablePointer<Type>, or AutoreleasingUnsafeMutablePointer<Type>
或者轉換為UnsafePointer<Type>
類型的值。 -
String
類型的值,如果類型是Int8
或UInt8
,那字符串會自動轉換為UTF8的buffer,然后這個buffer的指針將會傳入函數。 - 表達式Type類型的變量、屬性、同類型的下標表達(如:a[0]),這樣的情況下,將左邊起始地址傳入函數。
-
[Type]
數組類型值,將數組起始地址傳入函數。
在調用函數時,必須保證傳入的值可用。
例子:
func takesAPointer(_ p: UnsafePointer<Float>) {
// ...
}
var x: Float = 0.0
takesAPointer(&x)
takesAPointer([1.0, 2.0, 3.0])
上面的例子中必須定義時必須指定一個類型,如:Float。可以使用UnsafeRawPointer
修飾,則可以傳入相同類型的Type。
例子:
func takesARawPointer(_ p: UnsafeRawPointer?) {
// ...
}
var x: Float = 0.0, y: Int = 0
takesARawPointer(&x)
takesARawPointer(&y)
takesARawPointer([1.0, 2.0, 3.0] as [Float])
let intArray = [1, 2, 3]
takesARawPointer(intArray)
可變指針
定義為UnsafeMutablePointer<Type>、UnsafeMutableRawPointer
的指針為可變指針,可以接收下面幾種類型的值:
- 類型為
UnsafeMutablePointer<Type>
類型的值。 - 表達式
Type
類型的變量、屬性、同類型的下標表達(如:a[0]),這樣的情況下,將地址傳入函數。 - 表達式
[Type]
類型的變量、屬性、同類型的下標表達,這樣的情況下,將左邊起始地址傳入函數。
func takesAMutablePointer(_ p: UnsafeMutablePointer<Float>) {
// ...
}
var x: Float = 0.0
var a: [Float] = [1.0, 2.0, 3.0]
takesAMutablePointer(&x)
takesAMutablePointer(&a)
// 不指定具體類型
func takesAMutableRawPointer(_ p: UnsafeMutableRawPointer?) {
// ...
}
var x: Float = 0.0, y: Int = 0
var a: [Float] = [1.0, 2.0, 3.0], b: [Int] = [1, 2, 3]
takesAMutableRawPointer(&x)
takesAMutableRawPointer(&y)
takesAMutableRawPointer(&a)
takesAMutableRawPointer(&b)
Autoreleasing Pointers(自動釋放指針)
定義為AutoreleasingUnsafeMutablePointer<Type>
,可以接收下面幾種類型的值:
-
AutoreleasingUnsafeMutablePointer<Type>
的值 - 表達式
Type
類型的變量、屬性、同類型的下標表達(如:a[0]),這里會按位拷貝到臨時緩沖區,緩沖區的值會被加載,retain,分配值。
注意:不能傳數組
例子:
func takesAnAutoreleasingPointer(_ p: AutoreleasingUnsafeMutablePointer<NSDate?>) {
// ...
}
var x: NSDate? = nil
takesAnAutoreleasingPointer(&x)
函數指針
在C中有回調函數,當swift要調用C中這類函數時,可以使用函數指針。
swift中可以用@convention 修飾一個閉包,
- @convention(swift) : 表明這個是一個swift的閉包
- @convention(block) :表明這個是一個兼容oc的block的閉包,可以傳入OC的方法。
- @convention(c) : 表明這個是兼容c的函數指針的閉包,可以傳入C的方法。
C中的方法int (*)(void)
在swift中就是@convention(c) () -> Int32
在調用C函數需要傳入函數指針時,swift可以傳入閉包的字面量或者nil,也可以直接傳入一個閉包。
例如:
func customCopyDescription(_ p: UnsafeRawPointer?) -> Unmanaged<CFString>? {
// return an Unmanaged<CFString>? value
}
var callbacks = CFArrayCallBacks(
version: 0,
retain: nil,
release: nil,
copyDescription: customCopyDescription,
equal: { (p1, p2) -> DarwinBoolean in
// return Bool value
}
)
var mutableArray = CFArrayCreateMutable(nil, 0, &callbacks)
上面的例子中,retain、release是傳入的nil,copyDescription是傳入的函數字面量,equal是直接傳入的閉包。
Buffer Pointers
buffer指針用于比較底層的內存操作,你可以使用buffer指針做高效的處理和應用程序與服務間的通信。
有下面幾種類型的buffer指針
- UnsafeBufferPointer
- UnsafeMutableBufferPointer
- UnsafeRawBufferPointer
- UnsafeMutableRawBufferPointer
UnsafeBufferPointer、 UnsafeMutableBufferPointer
,能讓你查看或更改一個連續的內存塊。
UnsafeRawBufferPointer、UnsafeMutableRawBufferPointer
能讓你查看或更改一個連續內存塊的集合,集合里面每個值對應一個字節的內存。
指針用法
當使用指針實例的時候,可以使用pointee
屬性獲取指針指向內容的值,指針不會自動管理內存或對準擔保。你必須自己管理生命周期以避免泄露和未定義的行為。
內存可能有幾種狀態:未指定類型未初始化、指定類型未初始化、指定類型已初始化。
- 未分配的:沒有預留的內存分配給指針
- 已分配的:指針指向一個有效的已分配的內存地址,但是值沒有被初始化。
- 已初始化:指針指向已分配和已初始化的內存地址。
指針將根據我們具體的操作在這 3 個狀態之間進行轉換。
用法示例
未分配的指針用allocate
方法分配一定的內存空間。
let uint8Pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: 8)
分配完內存空間的指針用各種init方法來綁定一個值或一系列值。初始化時,必須保證指針是未初始化的。(初始化過的指針可以再次調用初始化方法不會報錯,所以使用時需要特別注意。)
uint8Pointer.initialize(to: 20, count: 8)
print(uint8Pointer[0]) // 20
然后修改值
uint8Pointer[0] = 10
print(uint8Pointer[0]) // 10
回到初始化值之前,沒有釋放指針指向的內存,指針依舊指向之前的值。
uint8Pointer.deinitialize(count: 8)
釋放指針指向的內存,據官方文檔說,在釋放指針內存之前,必須要保證指針是未初始化的,不然會產生問題。
uint8Pointer.deallocate(capacity: 8)
print(uint8Pointer[0]) // 可能是任何值,已經銷毀了
其他用法
看個栗子:
struct MyStruct1{
var int1:Int
var int2:Int
}
var s1ptr = UnsafeMutablePointer<MyStruct1>.allocate(capacity: 5)
s1ptr[0] = MyStruct1(int1: 1, int2: 2)
s1ptr[1] = MyStruct1(int1: 1, int2: 2) // 似乎不應該是這樣,但是這能夠正常工作
s1ptr.deinitialize(count: 5)
s1ptr.deallocate(capacity: 5)
這個栗子中沒有使用init方法來初始化指針,但是可以正常工作。不過這樣寫并不推薦,它不適用指針指向一個類,或某些特定的結構體和枚舉的情況。
why?
當你使用上面提及的方式修改內存內容,從內存管理角度來說,有關這種行為背后的原因和發生時有關的。讓我們來看一個不需要手動初始化內存的代碼片段,倘若我們在沒有初始化 UnsafePointer 情況下改變了指針指向的內存,會引發崩潰。
class TestClass{
var aField:Int = 0
}
struct MyStruct2{
var int1:Int
var int2:Int
var tc:TestClass // 這個字段是引用類型
}
var s2ptr = UnsafeMutablePointer<MyStruct2>.allocate(capacity: 5)
s2ptr.initialize(to: MyStruct2(int1: 1, int2: 2, tc: TestClass()), count: 2) // 刪除這行初始化代碼將引發崩潰
s2ptr[0] = MyStruct2(int1: 1, int2: 2, tc: TestClass())
s2ptr[1] = MyStruct2(int1: 1, int2: 2, tc: TestClass())
s2ptr.deinitialize(count: 5)
s2ptr.deallocate(capacity: 5)
MyStruct2 包含一個引用類型,所以它的生命周期交由 ARC 管理。當我們修改其中一個指向的內存模塊值的時候,Swift 運行時將試圖釋放之前存在的對象,由于這個對象沒有被初始化,內存存在垃圾,你的應用將會崩潰。
請牢記這一點,從安全的角度來講,最受歡迎的初始化手段是使用 initialize 分配完成內存后,直接設置變量的初始值。
轉換
可變 不可變
當一個函數需要傳入不可變指針時,可變指針可以直接傳入。
而當一個函數需要可變指針時,可以使用init(mutating other: UnsafePointer<Pointee>)
方法轉換
var i: Int8 = 12
func printPointer(p: UnsafePointer<Int8>) {
let muS2ptr = UnsafeMutablePointer<Int8>.init(mutating: p)!
print(muS2ptr.pointee)
}
printPointer(p: &i)
各種類型轉換:
var i: Int8 = 12
func printPointer(p: UnsafePointer<Int8>) {
let muS2ptr = UnsafeMutablePointer<Int8>.init(mutating: p)! // UnsafePointer<Int8> -> UnsafeMutablePointer<Int8>
print(muS2ptr.pointee)
var constUnTypePointer = UnsafeRawPointer(p) // UnsafePointer<Int8> - > UnsafeRawPointer
var unTypePointer = UnsafeMutableRawPointer(mutating: constUnTypePointer) // UnsafeRawPointer -> UnsafeMutableRawPointer
var unTypePointer2 = UnsafeMutableRawPointer(muS2ptr) // UnsafeMutablePointer<Int8> -> UnsafeMutableRawPointer
}
指針可以使用load等方法轉為對應的類型
func print<T>(address p: UnsafeRawPointer, as type: T.Type) {
let value = p.load(as: type)
print(value)
}
不同類型
下面的例子展示了將Uint8的指針 轉換為UInt64類型。
var i: UInt8 = 125
func printPointer(uint8Pointer: UnsafePointer<UInt8>) {
let pointer0 = UnsafeRawPointer(uint8Pointer)
.bindMemory(to: UInt64.self, capacity: 1)
let pointer0Value = pointer0.pointee
print(pointer0Value) // UInt64
let pointer1 = UnsafeRawPointer(uint8Pointer).assumingMemoryBound(to: UInt64.self)
let pointer1Value = pointer1.pointee
print(pointer1Value) // UInt64
let pointer2Value = UnsafeRawPointer(uint8Pointer).load(as: UInt64.self)
print(pointer2Value) // UInt64
let pointer3 = UnsafeMutablePointer(mutating: uint8Pointer).withMemoryRebound(to: UInt64.self, capacity: 1) { return $0 }
print(pointer3.pointee) // UInt64
}
printPointer(uint8Pointer: &i)
對象轉換
在調用一些底層庫的時候,經常要把Swift對象傳入C中,然后將從C中的回調函數中轉換成Swift對象。
下面是我自己寫的一個例子:
定義的C代碼是:
// c的頭文件
#include <stdio.h>
typedef struct {
void *info;
const void *(*retain)(const void *info);
}Context;
void abcPrint(Context *info, void (*callback)(void *));
// c 的實現文件
void abcPrint(Context *info, void (*callback)(void *)){
(*callback)(info->info);
printf("abcPrint call");
}
上面的代碼context中有個info可以帶void *
對象, 然后在回調方法中將info對象返回回來。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var blockSelf = self
let controllerPoint = withUnsafeMutablePointer(to: &blockSelf) { return $0}
print("\(self)")
var context = Context(info: controllerPoint, retain: nil)
abcPrint(&context) {
let controller1 = $0?.assumingMemoryBound(to: ViewController.self).pointee
print("controller1: \(String(describing: controller1))")
let controller2 = $0?.bindMemory(to: ViewController.self, capacity: 1).pointee
print("controller2: \(String(describing: controller2))")
let controller3 = $0?.load(as: ViewController.self)
print("controller3: \(String(describing: controller3))")
}
}
}
withUnsafeMutablePointer
方法可以將Swift對象ViewController
轉換為UnsafeMutablePointer<ViewController>
類型,這樣才可以當做參數傳入C函數。
C函數的回調函數中,傳出來一個UnsafeMutableRawPointer
對象的指針,我展示了3種方式,可以將這個指針轉換為ViewController對象。
CFRoopLoop使用
相信有的人看到上面的代碼,知道我為什么會寫這個知識點了。因為我在使用CFRunLoop優化大圖加載的時候遇到了這樣的一個需求。
當創建CFRunLoopObserverContext
時需要傳入Swift對象到info屬性,在CFRunLoopObserverCreate
的回調函數中,會把這個info返回回來使用。這里就需要這樣的類型轉換。
Unmanaged方式
蘋果的一些底層框架返回的對象有的是自動管理內存的(annotated APIs),有的是不自動管理內存。
對于Core Fundation中有@annotated注釋的函數來說,返回的是托管對象,無需自己管理內存,可以直接獲取到CF對象,并且可以無縫轉化(toll free bridging)成Fundation對象,比如NSString和CFString。目前,內存管理注釋正在一步步的完善,所以等到未來某一個版本我們就可以完完全全的像使用Fundation一樣使用Core Fundation啦。
-
對于尚未注釋的函數來說,蘋果給出的是使用非托管對象
Unmanaged<T>
進行管理的過渡方案。
當我們從CF函數中獲取到Unmanaged<T>
對象的時候,我們需要調用takeRetainedValue
或者takeUnretainedValue
獲取到對象T。具體使用哪一個方法,蘋果提出了Ownership Policy,具體來說就是:- 如果一個函數名中包含
Create
或Copy
,則調用者獲得這個對象的同時也獲得對象所有權,返回值Unmanaged
需要調用takeRetainedValue()
方法獲得對象。調用者不再使用對象時候,Swift代碼中不需要調用CFRelease函數放棄對象所有權,這是因為Swift僅支持ARC內存管理,這一點和OC略有不同。 - 如果一個函數名中包含Get,則調用者獲得這個對象的同時不會獲得對象所有權,返回值Unmanaged需要調用takeUnretainedValue()方法獲得對象。
- 如果一個函數名中包含
下面是我自己的試驗的代碼:
func addRunloopOberver1() {
let controllerPoint = Unmanaged<ViewController>.passUnretained(self).toOpaque()
var content = CFRunLoopObserverContext(version: 0, info: controllerPoint, retain: nil, release: nil, copyDescription: nil)
runloopObserver = CFRunLoopObserverCreate(nil, CFRunLoopActivity.beforeWaiting.rawValue, true, 0, { (oberver, activity, info) in
if info == nil {//如果沒有取到 直接返回
return
}
let controller = Unmanaged<ViewController>.fromOpaque(info!).takeUnretainedValue()
if controller.isKind(of: PicturesDetailViewController.self) {
controller.runloopCall()
}
}, &content)
CFRunLoopAddObserver(runloop, runloopObserver, CFRunLoopMode.commonModes)
}
注意: 如果這里使用takeRetainedValue 、passRetained
方法,會崩潰,我理解的是標明為retain的值,在使用后ARC會自動將它release一次,這樣在某個時候self對象就會被釋放了,當runloop再次用到self的時候就會崩潰。如果用unretain的值,ARC就不會去retain和release這個指針對象。
不建議的方式
看下面代碼:
func addRunloopOberver() {
let controllerPoint = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
var content = CFRunLoopObserverContext(version: 0, info: controllerPoint, retain: nil, release: nil, copyDescription: nil)
runloopObserver = CFRunLoopObserverCreate(nil, CFRunLoopActivity.beforeWaiting.rawValue, true, 0, { (oberver, activity, info) in
if info == nil {//如果沒有取到 直接返回
return
}
let controller = unsafeBitCast(info, to: PicturesDetailViewController.self)
if controller.isKind(of: ViewController.self) {
controller.runloopCall()
}
}, &content)
CFRunLoopAddObserver(runloop, runloopObserver, CFRunLoopMode.commonModes)
}
上面使用了unsafeBitCast
方法,強行將self對象轉換為指針,最后也是強行轉換回來。雖然說也可以運行,但是官方文檔中建議,不到萬不得已不要使用這個方法,特別危險。。
下面是官方文檔原文
Use this function only to convert the instance passed as x to a layout-compatible type when conversion through other means is not possible. Common conversions supported by the Swift standard library include the following:
Warning
Calling this function breaks the guarantees of the Swift type system; use with extreme care.
- Value conversion from one integer type to another. Use the destination type’s initializer or the numericCast(_:) function.
- Bitwise conversion from one integer type to another. Use the destination type’s init(truncatingIfNeeded:) or init(bitPattern:) initializer.
- Conversion from a pointer to an integer value with the bit pattern of the pointer’s address in memory, or vice versa. Use the init(bitPattern:) initializer for the destination type.
- Casting an instance of a reference type. Use the casting operators (as, as!, or as?) or the unsafeDowncast(:to:) function. Do not use unsafeBitCast(:to:) with class or pointer types; doing so may introduce undefined behavior.