Swift中的指針大法

本文概要

  • 指針的種類及區(qū)別
  • 不同指針間的相互轉(zhuǎn)換及常用方法
  • 各種類型的指針獲取及應(yīng)用
  • more than that

指針簡介

打開開發(fā)文檔,可以從Swift-->Swift Standard Library-->Manual Memory Management中找到指針類型:

指針類型

Swift中的指針類型分為兩種:Typed PointersRaw Pointers。

  • Typed Pointers
    typed pointer為類型指針,用于指向某種特定類型

  • Raw Pointers
    raw pointer為原始指針,未指明指向的類型,相當(dāng)于C語言中的void *

  • Mutable
    指針中含Mutable的為可變類型指針,表明可對指針指向的內(nèi)存進(jìn)行寫操作

  • Buffer
    指針中含Buffer的為緩沖類型指針,這類指針遵守了SequenceCollection協(xié)議,可以方便的進(jìn)行一些集合操作,如fliter、mapreduce

轉(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移動指針。
其中,successorpredecessor函數(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
UnsafeRawBufferPointer.png

前邊說過,原生指針以字節(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)}
buffer pointers.png

各種類型的指針獲取及應(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)
Struct實例
  1. 通過withUnsafeBytes獲取可變原生緩沖類型指針,可獲取到rich中每個字節(jié)的值
  2. 通過withUnsafeMutablePointer獲取到可變Rich類型指針后,轉(zhuǎn)為可變原生類型指針獲取rich地址
  3. 獲取moeny地址并賦值
  4. 獲取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)
Class實例
  1. 通過Unmanaged將rich轉(zhuǎn)換為可變原生類型指針
  2. 獲取money地址并賦值
  3. 獲取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!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容