因?yàn)槲也⒉皇怯?jì)算機(jī)專(zhuān)業(yè)出身,所以下面對(duì)C語(yǔ)言和指針的理解都來(lái)自于很久很久上過(guò)的C課程和工作以后接觸的oc以及swift。說(shuō)的不對(duì)的地方歡迎指正。
首先需要說(shuō)明的概念是比特(bit),理論上,這是一個(gè)計(jì)算系統(tǒng)最小的單位,不是0就是1,可以簡(jiǎn)單理解成為,通電和斷電。八個(gè)比特組成一個(gè)字節(jié)(byte),因?yàn)楝F(xiàn)代家用和商用的操作系統(tǒng)一般是32位或者64位系統(tǒng)。那么這是什么意思呢?不嚴(yán)謹(jǐn)?shù)睦斫饩褪遣僮飨到y(tǒng)指揮cpu一次性拾取這么多位數(shù)據(jù)。也就是說(shuō)32位系統(tǒng)一次從內(nèi)存讀取4個(gè)字節(jié)到32位cpu而64位系統(tǒng)一次讀取8個(gè)字節(jié)到cpu。讀到這里,作為一個(gè)工程師,你肯定會(huì)問(wèn)以下兩個(gè)問(wèn)題:
1. 那我要是一個(gè)數(shù)據(jù)占不滿(mǎn)這么多位怎么辦?。?2. 我要是數(shù)據(jù)超出了cpu怎么解決???
如果不是工程師,那么有可能會(huì)覺(jué)得問(wèn)這樣問(wèn)題的人是不是有病?沒(méi)滿(mǎn)就沒(méi)滿(mǎn)啊,滿(mǎn)了就再開(kāi)一個(gè)不就好了?。渴聦?shí)上,這涉及到一個(gè)最簡(jiǎn)單的工程思維,很多“常識(shí)性”的浪費(fèi)其實(shí)并不是真正浪費(fèi)。比如有一個(gè)叫做對(duì)齊的概念。懂一點(diǎn)計(jì)算機(jī)常識(shí)的人或多或少都聽(tīng)過(guò)這個(gè)概念,比如ssd的4k對(duì)齊。那么是內(nèi)存對(duì)齊呢?其實(shí)就是利用占位符(也就是0)來(lái)填滿(mǎn)沒(méi)有被使用的內(nèi)存空間,如果你要儲(chǔ)存一個(gè)int 10在內(nèi)存中,實(shí)際上是10 0 0 0 0 0 0 0 0,當(dāng)然,這里只是一個(gè)示例,顯然不可能是10存儲(chǔ)在內(nèi)存中。表面上看起來(lái)這是一種巨大的浪費(fèi),為什么我要占位而不能接著利用剩下來(lái)的空間呢?因?yàn)樵赾pu讀取的時(shí)候,對(duì)齊之后的數(shù)據(jù)可以大大降低讀寫(xiě)次數(shù),比如你要存儲(chǔ)“10個(gè)人”這三個(gè)字,如果我只想提取10出來(lái),那么我只需要找到指向integer的指針然后直接讀取所有步長(zhǎng)就可以了,這會(huì)很大程度上減少非必要的讀取次數(shù)。當(dāng)然除此以外,還有很多其他的原因比如原子性之類(lèi)的原因,具體的可以參考操作系統(tǒng)設(shè)計(jì)之類(lèi)的資料。那么當(dāng)我們說(shuō)讀取的時(shí)候是一個(gè)什么樣的概念呢?其實(shí)有點(diǎn)像送信,每個(gè)房屋都會(huì)在郵政有一個(gè)代碼,郵遞員就是根據(jù)這個(gè)代碼把郵件快遞送到你家的,每個(gè)位也有一個(gè)在操作系統(tǒng)注冊(cè)的代碼,操作系統(tǒng)就是依賴(lài)這個(gè)代碼來(lái)讓cpu去找到具體的數(shù)據(jù)的。
說(shuō)了這么多,所以到底什么是指針呢?指針就是一個(gè)整型數(shù)據(jù),儲(chǔ)存這個(gè)某一個(gè)數(shù)據(jù)在內(nèi)存的中的地址。一般人看到這里就會(huì)懵逼了,既然指針是指向數(shù)據(jù)的一個(gè)整數(shù),那么整數(shù)本身不也是一種數(shù)據(jù)嗎?你是不是在蒙我?這怎么聽(tīng)起來(lái)哪里不對(duì)?所以指針可以指向另外一個(gè)指針?答案就是這么粗暴,是這樣的。那你妹的不是太坑爹了嗎?所以你給了我一個(gè)整數(shù),我怎么知道這是一個(gè)數(shù)據(jù)還是一個(gè)指向數(shù)據(jù)的指針還是一個(gè)指向指針的指針?是的,你又猜對(duì)了,你不知道,至少對(duì)于swift來(lái)說(shuō),你不知道。很好,那怎么解決這個(gè)問(wèn)題呢?swift對(duì)c指針進(jìn)行了一些改造,給了你八種指針:
1. UnsafeMutablePointer<T>
2. UnsafePointer<T>
3. UnsafeMutableBufferPointer<T>
4. UnsafeBufferPointer<T>
5. UnsafeMutableRawPointer
6. UnsafeMutableRawBufferPointer
7. UnsafeRawBufferPointer
又是一臉懵逼,這都什么玩意?c 語(yǔ)言里面就一個(gè)指針,swift什么鬼?給我這么多名字比我太奶奶裹腳布太長(zhǎng)的東西?冷靜一下,仔細(xì)看看這些東西,其實(shí)還是很有規(guī)律的,pointer當(dāng)然表示這些都是指針,unsafe表示不安全,use on your own risk。這句話(huà)的意思是,swift本身是一門(mén)安全的語(yǔ)言,你們可勁作,崩了算我輸。編譯器負(fù)責(zé)開(kāi)創(chuàng),管理,銷(xiāo)毀內(nèi)存中的數(shù)據(jù),所以像我這樣非計(jì)算機(jī)專(zhuān)業(yè)出身的智障開(kāi)發(fā)者,要用什么開(kāi)什么就好了,完全沒(méi)有任何技術(shù)含量,不需要任何計(jì)算機(jī)常識(shí)。但是,這個(gè)的前提是編譯器來(lái)管理內(nèi)存而不是開(kāi)發(fā)者,如果你就是這么任性,不聽(tīng)不聽(tīng),飛鞋點(diǎn)金。那么,swift能怎么辦?只能貼個(gè)免責(zé)申明,對(duì)吧,人之常情,你也經(jīng)??吹绞裁础皟H供研究學(xué)習(xí),不得用于商業(yè)”之類(lèi)的東西,unsafe就是這個(gè)意思。作為ios開(kāi)發(fā)者,mutable還是可以看得懂的,generic也是看得懂的,所以只剩下raw和buffer了。Raw表示這個(gè)指針直接指向數(shù)據(jù)而不是指向其他指針!?。語(yǔ)言常識(shí)告訴我們,所有變量名,函數(shù)名,struct名都是指針。所以這就是為什么指向這些東西的指針實(shí)質(zhì)上是指向指針的指針。Buffer是什么意思呢?其實(shí)就是這個(gè)單詞本身的意思,你可以理解為一個(gè)array,表示這個(gè)一個(gè)指針群,可能包含不止一個(gè)指針。這樣一來(lái),我們已基本能夠通過(guò)名字知道該如何使用一個(gè)指針了。
還記得C語(yǔ)言是如何創(chuàng)建一個(gè)整數(shù)的嗎? int x = 8. 這行代碼說(shuō)明了什么呢?說(shuō)明了int這個(gè)東西是一個(gè)raw value,所以呢,如果想要?jiǎng)?chuàng)建一個(gè)指向int的指針,我們應(yīng)該使用UnsafeMutableRawPointer:
let intPoint = UnsafeMutableRawPointer.allocate(bytes: bytes, alignedTo: alignment)
通過(guò)系統(tǒng)的Documentaion,我們可以找到上面這個(gè)方法來(lái)初始化一個(gè)指針,那么這個(gè)函數(shù)名很直白的告訴你,這個(gè)函數(shù)的作用是在heap區(qū)allocate一個(gè)空間,空間包含有bytes位數(shù)據(jù),按照alignment方法對(duì)齊。那么我們?cè)侔凑誨ocumentation可以很清楚的得知如何去開(kāi)創(chuàng)一個(gè)內(nèi)存空間:
/*
MemoryLayout<Int>
MemoryLayout是一個(gè)抽象類(lèi),使用的時(shí)候需要告訴這個(gè)類(lèi)具體的type
你可以使用你自己的class或者struct,MemoryLayout<MyClass>來(lái)獲取相關(guān)內(nèi)存的信息。
對(duì)于MemoryLayout,我們有三個(gè)很重要的屬性:size,alignment,stride。
當(dāng)你開(kāi)創(chuàng)了空間以后系統(tǒng)會(huì)自動(dòng)給這三個(gè)屬性賦值,會(huì)告訴你,內(nèi)存塊大小是多少,對(duì)齊方式是怎么樣的,遞進(jìn)步長(zhǎng)是多少。
如果我們要?jiǎng)?chuàng)建一個(gè)指向一個(gè)Int類(lèi)型數(shù)據(jù)的指針,那么首先,Int是不會(huì)超過(guò)一個(gè)byte的,對(duì)齊方式是64位對(duì)齊。
*/
let bytes = MemoryLayout<Int>.stride
let alignment = MemoryLayout<Int>.alignment
let intPoint = UnsafeMutableRawPointer.allocate(bytes: bytes, alignedTo: alignment)
如果你說(shuō),這他么有什么用?好,那你聽(tīng)過(guò)數(shù)組嗎?我們通過(guò)指針來(lái)創(chuàng)建一個(gè)指定大小swift數(shù)組Array<UInt>:
class IntArray {
private let count:Int
private var index = -1
private var first = true
private var start: UnsafeMutableRawPointer!
private var bufferPointer: UnsafeRawBufferPointer!
private let stride = MemoryLayout<Int>.stride
private let alignment = MemoryLayout<Int>.alignment
private let q = DispatchQueue(label: "ReservedQ")
private var byteCount:Int {
get{return stride * count}
}
init(count:Int) {
self.count = count
start = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
}
func append(_ newElement:UInt) {
q.sync {
guard index < count else {return}
index += 1
if first {
first = false
start.storeBytes(of: newElement, as: UInt.self)
}else {
start.advanced(by: stride * index).storeBytes(of: newElement, as: UInt.self)
}
bufferPointer = UnsafeRawBufferPointer(start: start, count: byteCount)
for (i, byte) in bufferPointer.enumerated() {
guard i < (index+1)*8 else {break}
if byte != 0 && byte != 255 {
print(byte)
}else if i%8 == 0 && byte == 0 {
print(0)
}
}
print("--------------------")
}
}
deinit {
//這個(gè)函數(shù)是不可缺少的,這是unsafe的本質(zhì),you are on your own!你必須自己管理內(nèi)存了。
start.deallocate(bytes: byteCount, alignedTo: alignment)
}
}
運(yùn)行一下:
let arr = IntArray(count: 3)
arr.append(40)
arr.append(3)
arr.append(0)
結(jié)果是:
40
--------------------
40
3
--------------------
40
3
0
--------------------
當(dāng)然直接說(shuō)這個(gè)就是一個(gè)Array實(shí)在是太夸張了,但是,通過(guò)這么一個(gè)例子,我們可以更好的去理解如何在實(shí)際工作中去使用指針。畢竟對(duì)于iOS開(kāi)發(fā)來(lái)說(shuō),指針這種東西一般也就是用來(lái)寫(xiě)一寫(xiě)C函數(shù)的wrapper的。比如給keychain寫(xiě)一個(gè)wrapper,給一些第三方的SDK寫(xiě)一寫(xiě)Wrapper(可能在今天正常的公司都會(huì)給個(gè)最起碼是objective c的SDK,但是公司內(nèi)部的很多硬件SDK還是只有C和C++接口 T_T)。但是這并不表示指針這樣一樣強(qiáng)力工具對(duì)于iOS開(kāi)發(fā)就是不需要掌握的,因?yàn)槎羔樋梢宰屇阌煤芎?jiǎn)單的幾行代碼完成一些以前很難完成的工作。我們來(lái)看一個(gè)來(lái)自stackoverflow的例子:遍歷整個(gè)enum的所有case,如果你不會(huì)指針,那么你寫(xiě)出來(lái)的方案就會(huì)是:
enum ProductCategory : String {
case Washers = "washers", Dryers = "dryers", Toasters = "toasters"
static let allValues = [Washers, Dryers, Toasters]
}
for category in ProductCategory.allValues{
}
雖然看上去還可以,但是設(shè)想一下,如果你有十幾個(gè)case的話(huà),那么不僅很煩,而且還需要保證allValues array里面都是正確的。通過(guò)指針你就可以很輕松的使用一個(gè)static方法來(lái)完成:
enum ProductCategory : String {
case Washers = "washers", Dryers = "dryers", Toasters = "toasters"
static func iterateEnum() -> AnyIterator<ProductCategory> {
var i = 0
return AnyIterator {
let next = withUnsafeBytes(of: &i) { $0.load(as: self) }
if next.hashValue != i { return nil }
i += 1
return next
}
}
}
for category in ProductCategory.allValues{
}