OC指針
在OC中的對象Object我們都是用的指針殴玛,像下面這些:
NSString *str = ...;
NSObject *obj = ...;
NSArray *array = @[];
很顯然在OC中我們使用*
來表示對象擎宝,其實是聲明指針郁惜,而且使用&
符號來取地址水评,比如我們在使用C的數(shù)組時猩系,可以直接使用指針的+、- 來獲得當(dāng)前元素的下一個或上一個元素(這里是指針的加減而不是地址的加減中燥,指針+1可能地址跳了一位或者一個字節(jié)甚至更多):(代碼a0)
int nums[2] = {1,3,5};
printf("%d",*nums); //1
printf("%d",*(nums+1)); //3
//32513 = 0111 1111 0000 0001
int32_t num = 32513;
int8_t *num8s;
//取 num 地址賦給指針 num8s
num8s = #
//0000 0001 = 1
int num_1 = *num8s; //1
//0111 1111 = 127
int num_2 = *(num8s+1); //127
看上面代碼寇甸,我們給num
賦值32513,然后取num
地址賦給了指針num8s
疗涉,int8_t
這個類型是8位整形拿霉,這時候我們就相當(dāng)于把num
這個數(shù)字拆分為每8位為一個元素的數(shù)組(這里的int32_t
是4字節(jié),32位)咱扣,數(shù)組容量為4绽淘,我們用二進制表示的話就是:
0...0, 0...0, 01111111, 00000001
因為iphone是小端序,低地址存儲數(shù)值低位偏窝,所以拆成數(shù)組就是:
int8_t num8s[4] = {1,127,0,0};
我們獲取*num8s
相當(dāng)于num8s[0]
等于1收恢,獲取*(num8s+1)
相當(dāng)于num8s[1]
=127。
對于swift語言來說祭往,他也有指針,不過沒有OC指針那么方便的使用火窒,接下來我們講講swift里的指針使用硼补,以及用swift指針怎么實現(xiàn)上面那段代碼里的拆分內(nèi)存并讀取。
swift指針
首先先來了解下swift有哪幾種指針的類:
UnsafePointer
UnsafeRawPointer
UnsafeBufferPointer
UnsafeRawBufferPointer
//還有他們對應(yīng)的Mutable形式
UnsafeMutablePointer
...
mutable形式的指針有更多的可操作性熏矿,是以下講的重點已骇。
- UnsafeMutablePointer:常用指針類,要求綁定存儲的類型票编,通常的類對象褪储、實例對象、基本類型都可以用這種指針(類似
NSString *
,UILabel *
,NSObject *
)慧域。 - UnsafeMutableRawPointer:原始指針類鲤竹,不需要綁定存儲類型(類似
void *
)。 - UnsafeMutableBufferPointer:集合類對象指針昔榴,swift對集合類指針有單獨的函數(shù)和屬性(類似
NSArray<Pointee> *
,NSSet<Pointee> *
,NSDictionary<keyPointee, valuePointee> *
)辛藻,有count,startIndex互订,endIndex等屬性吱肌。 - UnsafeMutableRawBufferPointer:原始buffer指針,可以理解為BufferPointer的首地址仰禽。
指針的獲取
在OC中我們用&獲取指針氮墨,用(void *)number
強制類型轉(zhuǎn)換把整數(shù)轉(zhuǎn)為指針纺蛆,這就是我們在OC中指針的獲取和創(chuàng)建了,
那么我們在swift中如何獲取指針呢
首先swift中變量用var
聲明,常量用let
來聲明:
- let聲明的常量都存儲在數(shù)據(jù)區(qū)规揪,且不允許再改變值
- var聲明的變量犹撒,要看是什么類型,object類存在堆區(qū)粒褒,獨立的基本類型(附加在類中的屬性不算在此)存在棧中
舉例說明识颊,如下:代碼(b0)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//32513 = 0111 1111 0000 0001
// let number:Int16 = 32513
var number: Int16 = 32513
var label = UILabel()
label.text = "aHaha"
label.font = UIFont.systemFont(ofSize: 17)
}
}
當(dāng)number聲明為
let
時,他的地址是0x117e295b0的數(shù)據(jù)區(qū)地址奕坟;-
當(dāng)number聲明為
var
時祥款,他的地址就是0x7ffee69288a8的棧區(qū)地址,
此時number是直接尋址月杉,即棧地址對應(yīng)的內(nèi)存中直接存儲了數(shù)據(jù):
number的直接尋址圖.png -
而
var
聲明的label是間接尋址刃跛,局部變量指針是0x7ffee69288a0的棧地址,棧地址內(nèi)存儲的是堆地址0x7fdc0cd088b0,堆地址中存儲了label的實際內(nèi)容:
label的間接尋址圖.png
上面我們知道了對象變量大都存儲在堆中苛萎,獨立基本類型大都存儲在棧中桨昙,下面我們再看指針的獲取方法。
1.基本類型的指針
使用swift.Misc庫中withUnsafe...方法臨時獲取指針腌歉,前提必須是可變變量var
:
let raw_number = withUnsafePointer(to: &number, { bb -> Int in
return Int(bitPattern: bb)
})
如果是聲明為let
的常量那么編譯會報錯蛙酪,因為let
常量存儲在數(shù)據(jù)區(qū),數(shù)據(jù)區(qū)除了常量還有APP代碼等翘盖,為了安全性考慮不允許修改內(nèi)容(在debug時桂塞,let
常量可以在控制臺輸出po withUnsafePointer(to: &number, {bb->Int in return Int(bitPattern: bb)})
):
需要注意的是我們這里return出來的是 Int 值,原因是
withUnsafePointer
這類函數(shù)的閉包中獲取的指針都是臨時指針馍驯,不能在閉包外使用阁危,這Int值我們可以繼續(xù)處理成指針:
let number_pointer = UnsafePointer<Int16>.init(bitPattern: raw_number)
number_pointer?.pointee == number //true
2.object對象的指針
我們拿 代碼(b0) 中的label舉例,label是一個對象汰瘫,那么獲取指針的方法就很多了:
let label_UnsafeMutableRawPointer = Unmanaged.passUnretained(label).toOpaque()
let label_unsafeBitCast_Int = unsafeBitCast(label, to: Int.self)
let with_raw_label = withUnsafeMutablePointer(to: &label) { (wp) -> Int in
return Int(bitPattern: wp)
}
//對于直接尋址的number來說狂打,使用unsafeBitCast方法獲取到的就是number局部變量對應(yīng)指針的內(nèi)存中的值,
//因為是直接尋址混弥,所以就是數(shù)值 32513
let number_bitCast = unsafeBitCast(number, to: Int16.self)
- 對于實例對象label來說趴乡,可以使用非托管類的
passUnretained
方法來創(chuàng)建一個非托管對象,然后獲取其原始指針地址剑逃。 - 使用swift.C庫函數(shù)
unsafeBitCast
浙宜,這個方法就是獲取label局部變量指針對應(yīng)內(nèi)存中的值,因為是間接尋址蛹磺,實際獲得的是label對應(yīng)在堆內(nèi)存中的地址(這里返回的是Int值)140583084525744粟瞬,轉(zhuǎn)為16進制就是7fdc0cd088b0(在上文的 label間接尋址圖 有)。 - 使用swift.Misc庫的
withUnsafeMutablePointer
函數(shù)萤捆,獲取臨時指針裙品,這里獲取到的是label局部變量指針即棧指針140732766783648俗批,轉(zhuǎn)16進制就是7ffee69288a0(在 label的間接尋址圖 有),再通過Int(bitPattern:)
函數(shù)獲取到指針地址的Int值市怎。
對于上面獲取的label_UnsafeMutableRawPointer
岁忘,我們可以轉(zhuǎn)為常用的指針類型UnsafeMutablePointer
:(代碼b1)
let heap_labelPointer = label_UnsafeMutableRawPointer.bindMemory(to: UILabel.self, capacity: 1)
對于我們獲取到的label的兩個指針地址Int值:棧指針0x7ffee69288a0和堆指針0x7fdc0cd088b0(指針地址的Int值,不是指針)区匠,到底怎么使用干像,使用哪個呢?看:(代碼b2)
let stack_labelPointer = UnsafeMutablePointer.init(bitPattern: with_raw_label)
let heap_labelPointer = UnsafeMutablePointer.init(bitPattern: label_unsafeBitCast_Int)
stack_labelPointer.pointee == label //true
heap_labelPointer.pointee == label //false
對于指針UnsafeMutablePointer來說驰弄,指針本身作為一個結(jié)構(gòu)體有一個內(nèi)存麻汰,結(jié)構(gòu)體中的pointee用來存儲指針?biāo)赶虻膬?nèi)容。
上面代碼中stack_labelPointer
是用label的臨時指針地址初始化的戚篙,stack_labelPointer.pointee
就是label五鲫;
而heap_labelPointer.pointee
卻不是label,要知道label_unsafeBitCast_Int
是label在堆中的存儲地址岔擂,所以我們用label_unsafeBitCast_Int
來初始化指針導(dǎo)致這個結(jié)構(gòu)體本身就是label(因為結(jié)構(gòu)體和類的一些相似性位喂,不會報錯),pointee就不是指向label了乱灵。
那么heap_labelPointer.pointee
不是label是什么呢:(代碼b3)
let what = heap_labelPointer!.pointee
//這里正常輸出塑崖,label不是一個類對象,而是一個實例對象
let label_notClassObject = label as? AnyClass == .none
//下面兩句都是true,第一句證明what是UILabel的類對象阔蛉,
//第二句證明what可以調(diào)用UILabel的類方法areAnimationsEnabled,
//綜上what確實是UILabel類對象
let what_isUILabelClass = what as? AnyClass == Optional.some(UILabel.self)
let what_hadUILabelClassMethod:String = what.responds(to: #selector(getter: UILabel.areAnimationsEnabled)) ? "true":"false"
通過上面的代碼我們發(fā)現(xiàn)heap_labelPointer.pointee
就相當(dāng)于在OC中的'isa'弃舒,OC中l(wèi)abel對象的isa是UILabel,這里 pointee 也是指向UILabel類對象状原,并且可以調(diào)用UILabel的類方法。
這里僅當(dāng)是OC中存在的NSObject時苗踪,堆指針地址的.pointee
才是指向類對象颠区,如果是其他對象,那么在強制轉(zhuǎn)為指針的過程中通铲,其 ‘pointee’ 在指針結(jié)構(gòu)體中的偏移地址(為0) 對應(yīng)在內(nèi)存中地址就是.pointee
(作為NSObject時毕莱,pointee偏移地址為0,剛好對應(yīng)isa)。
3.數(shù)組的指針
var numbers:[Int] = [7,3,2,4,0]
對于數(shù)組numbers來說颅夺,他的存儲有兩部分朋截,一部分是數(shù)組的標(biāo)識、屬性等吧黄,一部分是數(shù)組元素部服,一般數(shù)組元素就存儲在數(shù)組標(biāo)識、屬性等的相鄰內(nèi)存(鄰接數(shù)組ContiguousArray就是這種存儲方式)拗慨±耍看:(代碼c0)
//獲取局部變量numbers的指針(&numbers)奉芦,棧指針
let stack_raw_numbers = withUnsafePointer(to: &numbers, { (bb) -> Int in
return Int(bitPattern: bb)
})
//numbers 存儲在堆中地址,該存儲地址不是數(shù)組元素首地址剧蹂,而是以數(shù)組標(biāo)識屬性等開始的數(shù)組內(nèi)存
let heap_raw_numbers = unsafeBitCast(numbers, to: Int.self)
//numbers 可以直接作為 UnsafeRawPointer 使用, 獲取 數(shù)組元素存儲的首地址
let raw_numbers = Int(bitPattern: numbers)
對于上面的三個指針地址声功,我們實際用到的一般只有兩個:stack_raw_numbers 和 raw_numbers ,這兩個就是獲取數(shù)組元素的兩種方法宠叼,使用如下:(代碼c1)
//1.取局部變量棧指針先巴,通過該指針的pointee來指向數(shù)組
var numbersPointer_test = UnsafeMutablePointer<[Int]>.init(bitPattern: stack_raw_numbers)
let get_numbers = numbersPointer_test!.pointee
//2.取數(shù)組元素存儲區(qū)的首地址,即數(shù)組第一個元素的地址
let numbersPointer_0 = UnsafeMutablePointer<Int>.init(bitPattern: raw_numbers)
//之后的元素可以根據(jù)元素地址冒冬,向后推移
let numbersPointer_1 = numbersPointer![1]
let numbersPointer_2 = numbersPointer![2]
//也可以直接使用元素首地址初始化bufferPointer
var numbers_bufferPointer = UnsafeMutableBufferPointer.init(start: numbersPointer, count: 5)
- 使用
stack_raw_numbers
來初始化指針可以直接用pointee; - 使用數(shù)組元素首地址初始化指針伸蚯,該指針指向第一個元素,第二個元素可以用numbersPointer![1]來表示窄驹,以此類推朝卒;
還可以根據(jù)首地址初始化numbers_bufferPointer,numbers_bufferPointer[0] == 7,numbers_bufferPointer[1] == 3 ...
對于heap_raw_numbers
來說乐埠,他是數(shù)組的首地址(直接指向數(shù)組標(biāo)識抗斤、屬性等),但是數(shù)組內(nèi)元素實際上是存儲在旁邊內(nèi)存里(鄰接數(shù)組的元素存儲在相鄰內(nèi)存丈咐,一般會相差4個字節(jié)):
//這里的指針本身就是指向數(shù)組了瑞眼,這導(dǎo)致調(diào)用pointee反而出錯,pointee并不指向數(shù)組元素
var what_numbers_pointer = UnsafeMutablePointer<[Int]>.init(bitPattern: heap_raw_numbers)
//跟 代碼b3 類似棵逊,不過這里不是OC對象NSArray伤疙,而是swift對象Array
let some_what_numbersPointer = what_numbers_pointer!.pointee
指針的創(chuàng)建
上面我們講了根據(jù)現(xiàn)有對象初始化指針,這里我們講指針的創(chuàng)建辆影,這兩者最大的區(qū)別就是指針創(chuàng)建需要申請內(nèi)存徒像,這個指針如果使用label來初始化,那他也不是原來的label了蛙讥,他是一個新的 存儲在指針 .pointee 內(nèi)的對象锯蛀。
指針一般用allocte
來創(chuàng)建,需要注意的是capacity參數(shù)是容量的意思次慢,非集合指針一般設(shè)為1,
struct SYPerson {
var name: String
var age: Int
}
let systructPointer = UnsafeMutablePointer<SYPerson>.allocate(capacity: 1)
用initialize
來初始化旁涤,如果類型是基本類型,那么可以直接assign迫像,因為內(nèi)存中保存的必定是0或1劈愚,相當(dāng)于已經(jīng)初始化了。
initialize(repeating: obj, count: 1)
可以用 initialize(to: obj)
代替闻妓。
//這里是自定義結(jié)構(gòu)體SYPerson菌羽,直接寫assign會崩潰,因為pointee還沒有申請內(nèi)存纷闺,無法拷貝內(nèi)容進去
//systructPointer.assign(repeating: systruct, count: 1) //wrong
systructPointer.initialize(repeating: systruct, count: 1)
systructPointer.initialize(to: systruct)
然后用assign
來分配內(nèi)存算凿,如果指針容量大于1份蝴,assign就會將repeating拷貝進內(nèi)存并循環(huán)拷貝count次,count就是repeating的重復(fù)次數(shù)
systructPointer.assign(repeating: systruct_other, count: 1)
//等同于
systructPointer[0] = systruct
//等同于
systructPointer.pointee = systruct
最后在不用的時候deallocate
釋放內(nèi)存氓轰。
systructPointer.deallocate()
實際使用過程中婚夫,所謂allocte
就是申請一個指針內(nèi)存,但是這個指針沒有為.pointee
準(zhǔn)備好內(nèi)存署鸡,而initialize
有為pointee
申請內(nèi)存并初始化內(nèi)容案糙;assign
則是將新的內(nèi)容拷貝到pointee
的內(nèi)存中,
assign
是不能用在未初始化的指針上的靴庆,initialize
方法介紹上說應(yīng)該使用在未初始化的指針上时捌,但是實際上可以作用在已初始化的指針上。
指針的使用
1.使用下標(biāo)來直接取值:
let systruct = SYPerson(name: "sd", age: 12)
let systructPointer = UnsafeMutablePointer<SYPerson>.allocate(capacity: 1)
systructPointer.initialize(to: systruct)
systructPointer[0].age == 12 //true
對于上面的代碼炉抒,我們可以直接用下標(biāo)取pointee值:systructPointer[0]
奢讨。
原變量systruct是let
修飾的,但是systructPointer.pointee
是新的變量焰薄,是可以修改的:systructPointer[0].name = "changed"
拿诸。
var numbers:[Int] = [7,3,2,4,0]
//numbers 可以直接作為 UnsafeRawPointer 使用, 獲取 數(shù)組元素存儲的首地址
let raw_numbers = Int(bitPattern: numbers)
let numbersPointer = UnsafeMutablePointer<Int>.init(bitPattern: raw_numbers)
numbersPointer![0] == 7 //true
numbersPointer![1] == 3 //true
上面這段代碼是作用在原數(shù)組numbers上的指針,如果指針改變值塞茅,原數(shù)組也會改變亩码。
創(chuàng)建一個新的指針可以這樣:(代碼d0)
let numberPointer_other = UnsafeMutablePointer<Int>.allocate(capacity: 5)
numberPointer_other.initialize(from: &numbers, count: 5)
//等同于
numberPointer_other.initialize(from: numbers, count: 5)
numberPointer_other[0] == 7 //true
numberPointer_other[1] == 3 //true
上面這段代碼根據(jù)已有數(shù)組來創(chuàng)建容量為5的指針,這里使用兩種方式:
- 使用
&numbers
表示數(shù)組的局部變量野瘦,棧指針描沟,初始化的時候initialize方法會自動把數(shù)組元素拷貝過去。 - 直接使用
numbers
鞭光,numbers
可以作為UnsafeRawPointer
使用(數(shù)組元素的首地址)吏廉,相當(dāng)于以數(shù)組元素的首地址來初始化新的指針。
數(shù)組也可以用bufferPointer惰许,這在 代碼c0和c1 中已經(jīng)展示了迟蜜,不過方便點的方法還是獲取臨時指針:
numbers.withUnsafeBufferPointer { (bb) in
bb.first! == 7 //true
bb[0] == 7 //true
}
2.簡單的說一下指針的一些方法
其實我是故意把這些常用方法放到最后來講。
UnsafeMutablePointer:
.withMemoryRebound(to: Int8.self, capacity: 1) { (bb) -> UnsafeMutablePointer<Int8> in ...}
:臨時綁定內(nèi)存啡省,這里綁定了Int8類型,閉包返回Int8類型指針髓霞。
.deinitialize(count: 1)
:反初始化卦睹,獲得原始指針rawPointer。
.advanced(by: n)
:返回向后移位n位的指針方库,這里一位就表示一個指針類型內(nèi)存结序,如果指針是SYPerson類型,那么就是向后移動n個SYPerson纵潦,即MemoryLayout<SYPerson>.stride * n
字節(jié)數(shù)徐鹤。UnsafeMutableRawPointer:
.bindMemory(to: Int8.self, capacity: 1)
:返回 將原始指針綁定到類型Int8 的指針垃环。
.load(as: Int8.self)
:將原始指針內(nèi)存加載位Int8類型并返回值。UnsafeMutableBufferPointer:
.last
.first
:內(nèi)存緩沖區(qū)的第一和最后一個元素返敬。
index(...)
:獲取相應(yīng)位置的元素遂庄。
.baseAddress
:返回內(nèi)存緩沖區(qū)的首地址。
基本可以直接當(dāng)做數(shù)組來操作劲赠。UnsafeMutableRawBufferPointer:
.last
.first
:基本單位是一字節(jié)的Int8
(格外注意)涛目,如果存儲的是別的類型,需要重新綁定內(nèi)存凛澎。
.bindMemory(to: Int.self)
:返回重新綁定為Int類型的BufferPointer指針霹肝。
.baseAddress
:返回內(nèi)存緩沖區(qū)的首地址的原始地址。
3.使用swift指針模仿OC 代碼a0 操作
根據(jù)上面的指針方法塑煎,我們直接取得number指針沫换,綁定Int8類型,然后用advanced方法向后移一字節(jié)最铁,就得到了127:代碼d0
//32513 = 0111 1111 0000 0001
var number: Int16 = 32513
let raw_number = withUnsafePointer(to: &number, { bb -> Int in
return Int(bitPattern: bb)
})
let number_pointer = UnsafeMutablePointer<Int8>.init(bitPattern: raw_number)
//number_pointer![1] == 127 //true
let number_8_pointer = number_pointer?.advanced(by: 1)
number_8_pointer[0] == 127 //true
//重新創(chuàng)建指針
let unsafeMutablePointer = UnsafeMutablePointer<Int16>.allocate(capacity: 1)
unsafeMutablePointer.initialize(repeating: number, count: 1)
unsafeMutablePointer.withMemoryRebound(to: Int8.self, capacity: 2) { bb in
bb[1] == 127 //true
}
如果是OC中UIKit類對象讯赏,如UILabel,UIView等炭晒,我們可以直接用
view.hash
獲取原始直接的Int值待逞,如果這些類沒有重寫Hashable
協(xié)議,我們還可以用.hashValue
:
var label = UILabel()
let label_unsafeBitCast_Int = unsafeBitCast(label, to: Int.self)
let raw_value = label.hash
label_unsafeBitCast_Int == raw_value //true
swift指針相當(dāng)于擴展了OC指針网严,由原本的指向內(nèi)存的地址识樱,變?yōu)榱艘栽摰刂窞槠鹗嫉刂返慕Y(jié)構(gòu)體;由原本的數(shù)值型地址震束,轉(zhuǎn)變?yōu)榱藄wift中的一段內(nèi)存