本文概要
- 指針的種類及區(qū)別
- 不同指針間的相互轉(zhuǎn)換及常用方法
- 各種類型的指針獲取及應(yīng)用
- more than that
指針簡介
打開開發(fā)文檔,可以從Swift-->Swift Standard Library-->Manual Memory Management中找到指針類型:

Swift中的指針類型分為兩種:Typed Pointers和Raw Pointers。
Typed Pointers
typed pointer為類型指針,用于指向某種特定類型Raw Pointers
raw pointer為原始指針,未指明指向的類型,相當(dāng)于C語言中的void *Mutable
指針中含Mutable的為可變類型指針,表明可對指針指向的內(nèi)存進(jìn)行寫操作Buffer
指針中含Buffer的為緩沖類型指針,這類指針遵守了Sequence和Collection協(xié)議,可以方便的進(jìn)行一些集合操作,如fliter、map、reduce
轉(zhuǎn)換
以下例子用于說明這8種指針間如何相互轉(zhuǎn)換
- UnsafeMutableRawPointer
let count = 2
let alignment = MemoryLayout<Int>.alignment // 8
let stride = MemoryLayout<Int>.stride // 8
let byteCount = count * stride // 16
// 分配2個Int類型所需字節(jié)數(shù)給UnsafeMutableRawPointer指針
let mRawP = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)
// 使用defer確保釋放UnsafeMutableRawPointer指針,以防遺漏
defer {
mRawP.deallocate()
}
// 在mRawP中存儲Int類型數(shù)據(jù),257
mRawP.storeBytes(of: 257, as: Int.self)
// 在mRawP向后偏移8個字節(jié)的位置存儲Int類型數(shù)據(jù),100
mRawP.storeBytes(of: 100, toByteOffset: stride, as: Int.self)
// 獲取mRawP向后偏移8個字節(jié)位置的指針
let anotherMRawP = mRawP.advanced(by: stride)
print("--------\(type(of: mRawP))---------")
print(mRawP.load(as: Int.self)) // 257
print(mRawP.load(fromByteOffset: stride, as: Int.self)) // 100
print(anotherMRawP.load(as: Int.self)) // 100
可變原生指針通過storeBytes存儲數(shù)據(jù),load讀取數(shù)據(jù),advanced移動字節(jié)數(shù)
- UnsafeMutablePointer<Int>
// 將mRawP的內(nèi)存數(shù)據(jù)綁定到Int類型上,生成UnsafeMutablePointer<Int>指針
let mP = mRawP.bindMemory(to: Int.self, capacity: byteCount)
// 將mP內(nèi)存中的數(shù)據(jù)加1
mP.pointee += 1
// 將mP后一個指針中的內(nèi)存數(shù)據(jù)加1
(mP + 1).pointee += 1
print("--------\(type(of: mP))---------")
print(mP.pointee) // 258
print(mP.advanced(by: 1).pointee) // 101
print(mP.successor().pointee) // 101
print(mP.advanced(by: 1).predecessor().pointee) // 258
可變類型指針通過pointee存取數(shù)據(jù),advanced、successor、predecessor移動指針。
其中,successor與predecessor函數(shù)為指定類型指針特有的函數(shù)。successor指向下一個元素,predecessor指向上一個元素。
這里可能有點懵,為啥UnsafeMutableRawPointer中的advanced移動的是字節(jié)數(shù),而UnsafeMutablePointer<Int>中的advanced移動的是元素數(shù)?這是由數(shù)據(jù)類型決定的,因為原生指針不知道存儲元素的類型,所以指針以字節(jié)為單位移動。而指定類型指針知道存儲的元素類型,所以指針以元素為單位移動。
- UnsafeRawPointer與UnsafePointer<Int>
let rawP = UnsafeRawPointer(mRawP)
print("--------\(type(of: rawP))---------")
print(rawP.load(as: Int.self)) // 258
let p = rawP.bindMemory(to: Int.self, capacity: stride)
print("--------\(type(of: p))---------")
print(p.pointee) // 258
不可變指針,沒什么好說的
- 地址比較
let mRawPAddress = String(Int(bitPattern: mRawP), radix: 16)
let mPAddress = String(Int(bitPattern: mP), radix: 16)
let rawPAddress = String(Int(bitPattern: rawP), radix: 16)
let pAddress = String(Int(bitPattern: p), radix: 16)
print("--------address---------")
print(mRawPAddress == rawPAddress) // true
print(mRawPAddress == mPAddress) // true
print(mRawPAddress == pAddress) // true
雖然上文中的四種指針類型不相同,但顯然他們的地址是相同的,因為都指向同一塊內(nèi)存區(qū)域,只是讀取數(shù)據(jù)的方式不同而已。
- UnsafeRawBufferPointer
let rawBufferP = UnsafeRawBufferPointer(start: rawP, count: byteCount)
print("-------\(type(of: rawBufferP))--------")
let _ = rawBufferP.map{print($0)}
print("-------")
print(rawBufferP.load(as: Int.self)) // 258
print("-------")
print(rawBufferP.load(as: Int8.self)) // 2
print("-------")
print(rawBufferP.load(fromByteOffset: 1, as: Int8.self)) // 1

前邊說過,原生指針以字節(jié)為單位移動指針,所以map中打印出的為每個字節(jié)上的數(shù)據(jù)。如果直接以Int類型讀取rawBufferP中的值,顯然為258。但是如果以Int8類型,也就是字節(jié)為單位讀取rawBufferP中的值,此時為2,而rawBufferP后一個字節(jié)值為1。一個字節(jié)最大存儲數(shù)據(jù)為255,根據(jù)小段模式,此時數(shù)據(jù)為12(256進(jìn)制),轉(zhuǎn)換成十進(jìn)制就是258。
- UnsafeBufferPointer<Int>、UnsafeMutableRawBufferPointer、UnsafeMutableBufferPointer<Int>
let bufferP = rawBufferP.bindMemory(to: Int.self)
print("-------\(type(of: bufferP))--------")
let _ = bufferP.map{print($0)}
let mRawBufferP = UnsafeMutableRawBufferPointer(mutating: rawBufferP)
print("-------\(type(of: mRawBufferP))--------")
let _ = mRawBufferP.map{print($0)}
let mBufferP = UnsafeMutableBufferPointer(mutating: bufferP)
print("-------\(type(of: mBufferP))--------")
let _ = mBufferP.map{print($0)}

各種類型的指針獲取及應(yīng)用
- 可變類型指針的獲取
var a = 1
withUnsafeMutablePointer(to: &a){$0.pointee += 1}
print(a) // 2
var arr = [1, 2, 3]
withUnsafeMutablePointer(to: &arr){$0.pointee[0] += 1}
print(arr) // [2, 2, 3]
arr.withUnsafeMutableBufferPointer{$0[0] += 1}
print(arr) // [3, 2, 3]
通過withUnsafeMutablePointer獲取可變類指針,通過withUnsafeMutableBufferPointer獲取可變緩沖類型指針
- Struct實例指針的獲取
struct Rich {
var money: Int
var isRich: Bool
}
var rich = Rich(money: 99999999, isRich: true)
withUnsafeBytes(of: &rich) { bytes in
for byte in bytes {
print(byte)
}
}
print("---------------")
let richP = withUnsafeMutablePointer(to: &rich) { UnsafeMutableRawPointer($0) }
let moneyP = richP.assumingMemoryBound(to: Int.self)
moneyP.pointee = 0
print(rich.money)
let isRichP = richP.advanced(by: MemoryLayout<Int>.stride).assumingMemoryBound(to: Bool.self)
isRichP.pointee = false
print(rich.isRich)

- 通過withUnsafeBytes獲取可變原生緩沖類型指針,可獲取到rich中每個字節(jié)的值
- 通過withUnsafeMutablePointer獲取到可變Rich類型指針后,轉(zhuǎn)為可變原生類型指針獲取rich地址
- 獲取moeny地址并賦值
- 獲取isRich地址并賦值
這里或許有疑問,為什么要轉(zhuǎn)成原生指針?因為原生指針以字節(jié)為單位進(jìn)行操作,更方便獲取想要的元素指針。
- Class實例指針的獲取
class CRich {
var money = 0
var isRich = false
}
var crich = CRich()
withUnsafeBytes(of: &crich) { bytes in
for byte in bytes {
print(byte)
}
}
print("---------------")
let crichP = Unmanaged.passUnretained(crich as AnyObject).toOpaque()
let cmoneyP = crichP.advanced(by: 16).assumingMemoryBound(to: Int.self)
cmoneyP.pointee = 99999999
print(crich.money)
let cisRichP = crichP.advanced(by: 16 + MemoryLayout<Int>.stride).assumingMemoryBound(to: Bool.self)
cisRichP.pointee = true
print(crich.isRich)

- 通過Unmanaged將rich轉(zhuǎn)換為可變原生類型指針
- 獲取money地址并賦值
- 獲取isRich地址并賦值
這里應(yīng)該又出現(xiàn)幾個疑問:
1.通過withUnsafeBytes獲取到的可變原生緩沖類型指針打印出的字節(jié)似乎不對?
2.為什么要通過Unmanaged獲取指針而不是withUnsafeMutablePointer?
3.為什么獲取rich指針后要偏移16個字節(jié)才是money的地址?
- 值引用與類型引用
Swift中的Struct屬于值引用,而Class屬于類型引用。對于值引用,所見即所得,所以上文通過withUnsafeBytes可以獲取結(jié)構(gòu)體各字節(jié)的值,而類型引用實際是指針指向具體的類型,所以通過withUnsafeBytes獲取到的結(jié)果不對。
再從側(cè)面驗證一下這個結(jié)論:
print(MemoryLayout<Rich>.alignment) // 8
print(MemoryLayout<Rich>.size) // 9
print(MemoryLayout<Rich>.stride) // 16
print("--------------")
print(MemoryLayout<CRich>.alignment) // 8
print(MemoryLayout<CRich>.size) //8
print(MemoryLayout<CRich>.stride) //8
由于Rich是值引用,所以size為8 + 1等于9,由于內(nèi)存對齊,所以stride等于16。而CRich是類型引用,實際是個指針,因此size與stride都是8。
- Class的前16字節(jié)
1.在32-bit設(shè)備上,前4byte存儲類型信息,后8byte存儲引用計數(shù)器,共12byte
2.在64-bit設(shè)備上,前8byte存儲類型信息,后8byte存儲引用計數(shù)器,共16byte
因此,實際地址從16byte開始(以64-bit設(shè)備為例)
Class trick
既然說到這里,是否可以拿Class前8byte中存儲的類型信息玩些小把戲?
class Dog {
func description() {
print("This is a dog.")
}
}
class Cat {
func description() {
print("This is a cat.")
}
}
var dog = Dog()
var cat = Cat()
let dogP = Unmanaged.passUnretained(dog as AnyObject).toOpaque()
let catP = Unmanaged.passUnretained(cat as AnyObject).toOpaque()
catP.bindMemory(to: Dog.self, capacity: 8).pointee = dogP.load(as: Dog.self)
cat.description() // This is a dog.
由于Dog與Cat內(nèi)存分布相同,強(qiáng)制將cat前8byte中的類型信息替換成dog的類型信息,此時通過cat調(diào)用description函數(shù)實際是調(diào)用到dog中去。
其實Objective-C中通過object_setClass函數(shù)也可以玩這種把戲:
@interface Dog : NSObject
@end
@implementation Dog
- (NSString *)description {
return @"This is dog.";
}
@end
@interface Cat : NSObject
@end
@implementation Cat
- (NSString *)description {
return @"This is cat";
}
@end
Dog *dog = [Dog new];
Cat *cat = [Cat new];
object_setClass(cat, dog.class);
NSLog(@"%@", cat.description); // This is dog.
Have fun!