參考文獻(xiàn)
Swift結(jié)構(gòu)體指針操作
官方文檔
Swift 和 C 不得不說的故事
Swift指針和托管穿扳,你看我就夠了
Why?
swift中并不推薦對指針直接操作蹬屹,但畢竟iOS中有的庫還是使用C語言構(gòu)建的扮碧,在與C混編的時候磁椒,不可避免的要將對象轉(zhuǎn)換為C的指針類型以便傳值堤瘤。比如GCD,CF框架Runloop操作。
各種指針
對應(yīng)表
下面表中Type為類型占位浆熔,可替換為int等等
對于返回值本辐、變量、參數(shù)的指針:
C | Swift | example |
---|---|---|
const Type * | UnsafePointer<Type> | const int * 轉(zhuǎn)換為UnsafePointer <Int32> |
Type * | UnsafeMutablePointer<Type> | int * 轉(zhuǎn)換為UnsafeMutablePointer <Int32> |
對于類對象的指針
C | Swift |
---|---|
Type * const * | UnsafePointer<Type> |
Type * __strong * | UnsafeMutablePointer<Type> |
Type ** | AutoreleasingUnsafeMutablePointer<Type> |
對于無符號類型医增、
C | Swift |
---|---|
const void * | UnsafeRawPointer |
void * | UnsafeMutableRawPointer |
如果像不完整結(jié)構(gòu)體的這樣的c指針的值的類型無法用Swift來表示慎皱,則用OpaquePointer來表示
常量指針
定義為 UnsafePointer<Type>
的指針為常量指針,在C中有const修飾叶骨。
當(dāng)函數(shù)的參數(shù)修飾為 UnsafePointer<Type>
的時候茫多,可以接收下面幾種類型的值:
- 類型為
UnsafePointer<Type>, UnsafeMutablePointer<Type>, or AutoreleasingUnsafeMutablePointer<Type>
或者轉(zhuǎn)換為UnsafePointer<Type>
類型的值。 -
String
類型的值忽刽,如果類型是Int8
或UInt8
天揖,那字符串會自動轉(zhuǎn)換為UTF8的buffer,然后這個buffer的指針將會傳入函數(shù)跪帝。 - 表達(dá)式Type類型的變量今膊、屬性、同類型的下標(biāo)表達(dá)(如:a[0])伞剑,這樣的情況下斑唬,將左邊起始地址傳入函數(shù)。
-
[Type]
數(shù)組類型值黎泣,將數(shù)組起始地址傳入函數(shù)恕刘。
在調(diào)用函數(shù)時,必須保證傳入的值可用抒倚。
例子:
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>
類型的值姻政。 - 表達(dá)式
Type
類型的變量、屬性岂嗓、同類型的下標(biāo)表達(dá)(如:a[0])汁展,這樣的情況下,將地址傳入函數(shù)厌殉。 - 表達(dá)式
[Type]
類型的變量食绿、屬性、同類型的下標(biāo)表達(dá)公罕,這樣的情況下器紧,將左邊起始地址傳入函數(shù)。
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>
的值 - 表達(dá)式
Type
類型的變量铲汪、屬性、同類型的下標(biāo)表達(dá)(如:a[0])罐柳,這里會按位拷貝到臨時緩沖區(qū)掌腰,緩沖區(qū)的值會被加載,retain张吉,分配值齿梁。
注意:不能傳數(shù)組
例子:
func takesAnAutoreleasingPointer(_ p: AutoreleasingUnsafeMutablePointer<NSDate?>) {
// ...
}
var x: NSDate? = nil
takesAnAutoreleasingPointer(&x)
函數(shù)指針
在C中有回調(diào)函數(shù),當(dāng)swift要調(diào)用C中這類函數(shù)時肮蛹,可以使用函數(shù)指針士飒。
swift中可以用@convention 修飾一個閉包,
- @convention(swift) : 表明這個是一個swift的閉包
- @convention(block) :表明這個是一個兼容oc的block的閉包蔗崎,可以傳入OC的方法酵幕。
- @convention(c) : 表明這個是兼容c的函數(shù)指針的閉包,可以傳入C的方法缓苛。
C中的方法int (*)(void)
在swift中就是@convention(c) () -> Int32
在調(diào)用C函數(shù)需要傳入函數(shù)指針時芳撒,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是傳入的函數(shù)字面量舌菜,equal是直接傳入的閉包。
Buffer Pointers
buffer指針用于比較底層的內(nèi)存操作亦镶,你可以使用buffer指針做高效的處理和應(yīng)用程序與服務(wù)間的通信日月。
有下面幾種類型的buffer指針
- UnsafeBufferPointer
- UnsafeMutableBufferPointer
- UnsafeRawBufferPointer
- UnsafeMutableRawBufferPointer
UnsafeBufferPointer袱瓮、 UnsafeMutableBufferPointer
,能讓你查看或更改一個連續(xù)的內(nèi)存塊爱咬。
UnsafeRawBufferPointer尺借、UnsafeMutableRawBufferPointer
能讓你查看或更改一個連續(xù)內(nèi)存塊的集合,集合里面每個值對應(yīng)一個字節(jié)的內(nèi)存精拟。
指針用法
當(dāng)使用指針實例的時候燎斩,可以使用pointee
屬性獲取指針指向內(nèi)容的值,指針不會自動管理內(nèi)存或?qū)?zhǔn)擔(dān)保蜂绎。你必須自己管理生命周期以避免泄露和未定義的行為栅表。
內(nèi)存可能有幾種狀態(tài):未指定類型未初始化、指定類型未初始化师枣、指定類型已初始化怪瓶。
- 未分配的:沒有預(yù)留的內(nèi)存分配給指針
- 已分配的:指針指向一個有效的已分配的內(nèi)存地址,但是值沒有被初始化坛吁。
- 已初始化:指針指向已分配和已初始化的內(nèi)存地址。
指針將根據(jù)我們具體的操作在這 3 個狀態(tài)之間進(jìn)行轉(zhuǎn)換铐尚。
用法示例
未分配的指針用allocate
方法分配一定的內(nèi)存空間拨脉。
let uint8Pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: 8)
分配完內(nèi)存空間的指針用各種init方法來綁定一個值或一系列值。初始化時宣增,必須保證指針是未初始化的玫膀。(初始化過的指針可以再次調(diào)用初始化方法不會報錯,所以使用時需要特別注意爹脾。)
uint8Pointer.initialize(to: 20, count: 8)
print(uint8Pointer[0]) // 20
然后修改值
uint8Pointer[0] = 10
print(uint8Pointer[0]) // 10
回到初始化值之前帖旨,沒有釋放指針指向的內(nèi)存,指針依舊指向之前的值灵妨。
uint8Pointer.deinitialize(count: 8)
釋放指針指向的內(nèi)存,據(jù)官方文檔說解阅,在釋放指針內(nèi)存之前,必須要保證指針是未初始化的泌霍,不然會產(chǎn)生問題货抄。
uint8Pointer.deallocate(capacity: 8)
print(uint8Pointer[0]) // 可能是任何值,已經(jīng)銷毀了
其他用法
看個栗子:
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) // 似乎不應(yīng)該是這樣朱转,但是這能夠正常工作
s1ptr.deinitialize(count: 5)
s1ptr.deallocate(capacity: 5)
這個栗子中沒有使用init方法來初始化指針蟹地,但是可以正常工作。不過這樣寫并不推薦藤为,它不適用指針指向一個類怪与,或某些特定的結(jié)構(gòu)體和枚舉的情況。
why缅疟?
當(dāng)你使用上面提及的方式修改內(nèi)存內(nèi)容分别,從內(nèi)存管理角度來說遍愿,有關(guān)這種行為背后的原因和發(fā)生時有關(guān)的。讓我們來看一個不需要手動初始化內(nèi)存的代碼片段茎杂,倘若我們在沒有初始化 UnsafePointer 情況下改變了指針指向的內(nèi)存错览,會引發(fā)崩潰。
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) // 刪除這行初始化代碼將引發(fā)崩潰
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 管理倾哺。當(dāng)我們修改其中一個指向的內(nèi)存模塊值的時候,Swift 運(yùn)行時將試圖釋放之前存在的對象刽脖,由于這個對象沒有被初始化羞海,內(nèi)存存在垃圾,你的應(yīng)用將會崩潰曲管。
請牢記這一點却邓,從安全的角度來講,最受歡迎的初始化手段是使用 initialize 分配完成內(nèi)存后院水,直接設(shè)置變量的初始值腊徙。
轉(zhuǎn)換
可變 不可變
當(dāng)一個函數(shù)需要傳入不可變指針時,可變指針可以直接傳入檬某。
而當(dāng)一個函數(shù)需要可變指針時撬腾,可以使用init(mutating other: UnsafePointer<Pointee>)
方法轉(zhuǎn)換
var i: Int8 = 12
func printPointer(p: UnsafePointer<Int8>) {
let muS2ptr = UnsafeMutablePointer<Int8>.init(mutating: p)!
print(muS2ptr.pointee)
}
printPointer(p: &i)
各種類型轉(zhuǎn)換:
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等方法轉(zhuǎn)為對應(yīng)的類型
func print<T>(address p: UnsafeRawPointer, as type: T.Type) {
let value = p.load(as: type)
print(value)
}
不同類型
下面的例子展示了將Uint8的指針 轉(zhuǎn)換為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)
對象轉(zhuǎn)換
在調(diào)用一些底層庫的時候恢恼,經(jīng)常要把Swift對象傳入C中民傻,然后將從C中的回調(diào)函數(shù)中轉(zhuǎn)換成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 的實現(xiàn)文件
void abcPrint(Context *info, void (*callback)(void *)){
(*callback)(info->info);
printf("abcPrint call");
}
上面的代碼context中有個info可以帶void *
對象场斑, 然后在回調(diào)方法中將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
轉(zhuǎn)換為UnsafeMutablePointer<ViewController>
類型,這樣才可以當(dāng)做參數(shù)傳入C函數(shù)漏隐。
C函數(shù)的回調(diào)函數(shù)中喧半,傳出來一個UnsafeMutableRawPointer
對象的指針,我展示了3種方式青责,可以將這個指針轉(zhuǎn)換為ViewController對象薯酝。
CFRoopLoop使用
相信有的人看到上面的代碼,知道我為什么會寫這個知識點了爽柒。因為我在使用CFRunLoop優(yōu)化大圖加載的時候遇到了這樣的一個需求吴菠。
當(dāng)創(chuàng)建CFRunLoopObserverContext
時需要傳入Swift對象到info屬性,在CFRunLoopObserverCreate
的回調(diào)函數(shù)中浩村,會把這個info返回回來使用做葵。這里就需要這樣的類型轉(zhuǎn)換。
Unmanaged方式
蘋果的一些底層框架返回的對象有的是自動管理內(nèi)存的(annotated APIs)心墅,有的是不自動管理內(nèi)存酿矢。
對于Core Fundation中有@annotated注釋的函數(shù)來說榨乎,返回的是托管對象,無需自己管理內(nèi)存瘫筐,可以直接獲取到CF對象蜜暑,并且可以無縫轉(zhuǎn)化(toll free bridging)成Fundation對象,比如NSString和CFString策肝。目前肛捍,內(nèi)存管理注釋正在一步步的完善,所以等到未來某一個版本我們就可以完完全全的像使用Fundation一樣使用Core Fundation啦之众。
-
對于尚未注釋的函數(shù)來說拙毫,蘋果給出的是使用非托管對象
Unmanaged<T>
進(jìn)行管理的過渡方案。
當(dāng)我們從CF函數(shù)中獲取到Unmanaged<T>
對象的時候棺禾,我們需要調(diào)用takeRetainedValue
或者takeUnretainedValue
獲取到對象T缀蹄。具體使用哪一個方法,蘋果提出了Ownership Policy膘婶,具體來說就是:- 如果一個函數(shù)名中包含
Create
或Copy
缺前,則調(diào)用者獲得這個對象的同時也獲得對象所有權(quán),返回值Unmanaged
需要調(diào)用takeRetainedValue()
方法獲得對象悬襟。調(diào)用者不再使用對象時候衅码,Swift代碼中不需要調(diào)用CFRelease函數(shù)放棄對象所有權(quán),這是因為Swift僅支持ARC內(nèi)存管理古胆,這一點和OC略有不同肆良。 - 如果一個函數(shù)名中包含Get筛璧,則調(diào)用者獲得這個對象的同時不會獲得對象所有權(quán)逸绎,返回值Unmanaged需要調(diào)用takeUnretainedValue()方法獲得對象。
- 如果一個函數(shù)名中包含
下面是我自己的試驗的代碼:
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
方法棺牧,會崩潰,我理解的是標(biāo)明為retain的值朗儒,在使用后ARC會自動將它release一次颊乘,這樣在某個時候self對象就會被釋放了,當(dāng)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
方法恳不,強(qiáng)行將self對象轉(zhuǎn)換為指針檩小,最后也是強(qiáng)行轉(zhuǎn)換回來。雖然說也可以運(yùn)行烟勋,但是官方文檔中建議规求,不到萬不得已不要使用這個方法筐付,特別危險。阻肿。
下面是官方文檔原文
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.