swift進(jìn)階十:Mirror源碼解析

swift進(jìn)階 學(xué)習(xí)大綱

上一節(jié)台诗,我們簡(jiǎn)單體驗(yàn)Mirror反射機(jī)制香伴。

本節(jié)刃泡,通過(guò)源碼栅哀,深入探索Mirror反射 ??

  1. 初始化
  2. 屬性類(lèi)型
  3. 屬性個(gè)數(shù)
  4. 屬性值

  • Mirror(反射):
    可以動(dòng)態(tài)獲取類(lèi)型成員變量究西,在運(yùn)行時(shí)可以調(diào)用方法货葬、屬性等行為的特性

回顧Mirror簡(jiǎn)單使用

class HTPerson {
   var name = "ht"
   var age = 18
}

let p = HTPerson()
let mirror = Mirror(reflecting: p.self)

for pro in mirror.children {
   print("\(pro.label ?? ""):\(pro.value)")
}
  • 打印結(jié)果:
image.png

1. 初始化

我們知道蘸炸,OC中通過(guò)Runtime能輕松獲取屬性方法尖奔、協(xié)議等搭儒,但swiftMirror是如何獲取到屬性類(lèi)型穷当、個(gè)數(shù)的呢淹禾?

  • 帶著這些疑問(wèn)馁菜,我們?cè)?code>swift源碼中搜索Mirror.swift

    image.png

  • 我們搜索找到初始化方法public init(reflecting subject: Any),從這里開(kāi)始我們的探索:

    image.png

  • 首先,我們分析一下如何通過(guò)subject來(lái)獲取類(lèi)型铃岔。

2. 類(lèi)型獲取

  • 搜索_getNormalizedType:
    image.png

注意:

  • 源碼分析時(shí)汪疮,首先分析結(jié)構(gòu),結(jié)構(gòu)是由繼承鏈上的所有屬性共同決定的毁习。

  • 函數(shù)只是所有屬性服務(wù)的智嚷。所以我們的重心,就是先找到結(jié)構(gòu)纺且,嘗試重寫(xiě)結(jié)構(gòu)盏道,能使用自己的結(jié)構(gòu)完整讀取到相應(yīng)的內(nèi)容證明源碼分析的結(jié)果是正確的隆檀。

  • 在這個(gè)過(guò)程摇天,順道看一眼函數(shù)粹湃,在屬性操作時(shí)恐仑,回頭分析哪些函數(shù)操作了它,是如何實(shí)現(xiàn)的为鳄。
    這樣才能完整梳理清楚裳仆。(當(dāng)然,得花大量時(shí)間反復(fù)研究才行)

  • 根據(jù)源碼探索孤钦,以struct類(lèi)型為例歧斟,可知structMetaData結(jié)構(gòu)為:
image.png
  • 按照structMetadata格式,嘗試讀取struct的類(lèi)型:
    (get:仿照源碼中RelativeDirectPointer (相對(duì)位置指針)進(jìn)行偏移偏形,獲取真實(shí)內(nèi)存值)
struct StructMetadata {
    var kind:        Int
    var description: UnsafeMutablePointer<StructMetadataDesc>
}

struct StructMetadataDesc {
    var flags:              UInt32
    var parent:             UInt32  // 展示用Uint32代替静袖,實(shí)際是相同大小的結(jié)構(gòu)體,
    var name:               RelativeDirectPointer<CChar>
    // . . .  (當(dāng)前研究獲取屬性類(lèi)型俊扭,后面的屬性先不管)
}

struct RelativeDirectPointer<T>{
    var offset: Int32
    
    // 偏移offset位置队橙,獲取內(nèi)容指針
    mutating func get() -> UnsafeMutablePointer<T> {
        let offset = self.offset
        // withUnsafePointer獲取指針
        return withUnsafePointer(to: &self) { p in
            // UnsafeMutablePointer 返回T類(lèi)型對(duì)象的指針
            // UnsafeRawPointer將p指針轉(zhuǎn)換為未知類(lèi)型
            // numericCast將offset轉(zhuǎn)換為偏移單位數(shù)
            // advanced進(jìn)行內(nèi)存偏移
            // assumingMemoryBound綁定指針為T(mén)類(lèi)型
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }
        
    }
}

struct HTStruct {
    var age = 18
}

// 讀取將HTStuct指針內(nèi)容,賦值給StructMetadata  (unsafeBitCast: 通過(guò)字節(jié)讀取)
let p = unsafeBitCast(HTStruct.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)

// 讀取當(dāng)前name的內(nèi)容指針
let namePtr = p.pointee.description.pointee.name.get()

// name是CChar類(lèi)型萨惑,轉(zhuǎn)為字符串輸出
print(String(cString: namePtr))
  • 成功獲取HTStruct類(lèi)型:
    image.png

3. 屬性個(gè)數(shù)

  • 回到初始化方法,可以看到是通過(guò)_getChildCount獲取屬性個(gè)數(shù):

    image.png

  • 仿寫(xiě)代碼捐康,

struct StructMetadata {
    var kind:        Int
    var description: UnsafeMutablePointer<StructMetadataDesc>
}

struct StructMetadataDesc {
    var flags:                   UInt32
    var parent:                  UInt32  // 展示用Uint32代替,實(shí)際是相同大小的結(jié)構(gòu)體庸蔼,
    var name:                    RelativeDirectPointer<CChar>  // 不在乎具體類(lèi)型解总,就先用UnsafeRawPointer
    var accessFunctionPtr:       RelativeDirectPointer<UnsafeRawPointer>  // 不在乎具體類(lèi)型,就先用UnsafeRawPointer
    var fields:                  RelativeDirectPointer<UnsafeRawPointer>  // 不在乎具體類(lèi)型姐仅,就先用UnsafeRawPointer
    var numFields:               UInt32  // 屬性個(gè)數(shù)
    var fieldOffsetVectorOffset: UInt32
}

struct RelativeDirectPointer<T>{
    var offset: Int32

    // 偏移offset位置花枫,獲取內(nèi)容指針
    mutating func get() -> UnsafeMutablePointer<T> {
        let offset = self.offset
        // withUnsafePointer獲取指針
        return withUnsafePointer(to: &self) { p in
            // UnsafeMutablePointer 返回T類(lèi)型對(duì)象的指針
            // UnsafeRawPointer將p指針轉(zhuǎn)換為未知類(lèi)型
            // numericCast將offset轉(zhuǎn)換為偏移單位數(shù)
            // advanced進(jìn)行內(nèi)存偏移
            // assumingMemoryBound綁定指針為T(mén)類(lèi)型
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }

    }
}

struct HTStruct {
    var age = 18
}

// 讀取將HTStuct指針內(nèi)容刻盐,賦值給StructMetadata  (unsafeBitCast: 通過(guò)字節(jié)讀取)
let p = unsafeBitCast(HTStruct.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)

// 讀取當(dāng)前name的內(nèi)容指針
let namePtr = p.pointee.description.pointee.name.get()
let count = p.pointee.description.pointee.numFields

// name是CChar類(lèi)型,轉(zhuǎn)為字符串輸出
print(String(cString: namePtr))
print("HTStruct屬性個(gè)數(shù):\(count)")
  • 打印結(jié)果:


    image.png
  • 修改結(jié)構(gòu)體屬性個(gè)數(shù)后:

    image.png

成功的重寫(xiě)結(jié)構(gòu)劳翰,拿到屬性個(gè)數(shù)隙疚,下面,我們來(lái)分析屬性值的獲取

4. 屬性值

  • 屬性值的獲取磕道,也是在children獲取時(shí)得到供屉。我們循著路徑,找到對(duì)應(yīng)類(lèi)型的impl溺蕉,讀取description里面的fields伶丐,分析fields內(nèi)部結(jié)構(gòu),發(fā)現(xiàn)每個(gè)屬性是以FieldRecord格式進(jìn)行存儲(chǔ)疯特。 存放的位置在fields的最后哗魂。有幾個(gè)屬性就會(huì)新增幾條FieldRecord記錄,可通過(guò)內(nèi)存地址偏移獲取所有屬性名漓雅。
image.png
// 結(jié)構(gòu)體類(lèi)型
struct StructMetadata {
    var kind:        Int
    var description: UnsafeMutablePointer<StructMetadataDesc>
}

// 結(jié)構(gòu)體類(lèi)型的描述
struct StructMetadataDesc {
    var flags:                   UInt32
    var parent:                  UInt32  // 展示用Uint32代替录别,實(shí)際是相同大小的結(jié)構(gòu)體,
    var name:                    RelativeDirectPointer<CChar>  // 不在乎具體類(lèi)型邻吞,就先用UnsafeRawPointer
    var accessFunctionPtr:       RelativeDirectPointer<UnsafeRawPointer>  // 不在乎具體類(lèi)型组题,就先用UnsafeRawPointer
    var fields:                  RelativeDirectPointer<FieldDescription>  // 記錄所有屬性內(nèi)容
    var numFields:               UInt32   // 屬性個(gè)數(shù)
    var fieldOffsetVectorOffset: UInt32
}

// 記錄結(jié)構(gòu)體內(nèi)所有屬性的結(jié)構(gòu)
struct FieldDescription {
    var MangledTypeName:         RelativeDirectPointer<CChar>
    var Superclass:              RelativeDirectPointer<CChar>
    var Kind:                    UInt16
    var FieldRecordSize:         UInt16
    var NumFields:               UInt32
    var fields:                  FieldRecord // 連續(xù)存儲(chǔ)空間 (有幾個(gè)數(shù)據(jù),就會(huì)在后面添加幾個(gè)記錄抱冷,通過(guò)內(nèi)存平移讀取)
}

// 每個(gè)屬性的內(nèi)容
struct FieldRecord {
    var flag:                    Int32
    var mangledTypeName:         RelativeDirectPointer<CChar>
    var fieldName:               RelativeDirectPointer<CChar>   // 屬性名稱(chēng)
}

// 相對(duì)位移指針
struct RelativeDirectPointer<T>{
    var offset: Int32

    // 偏移offset位置崔列,獲取內(nèi)容指針
    mutating func get() -> UnsafeMutablePointer<T> {
        let offset = self.offset
        // withUnsafePointer獲取指針
        return withUnsafePointer(to: &self) { p in
            // UnsafeMutablePointer 返回T類(lèi)型對(duì)象的指針
            // UnsafeRawPointer將p指針轉(zhuǎn)換為未知類(lèi)型
            // numericCast將offset轉(zhuǎn)換為偏移單位數(shù)
            // advanced進(jìn)行內(nèi)存偏移
            // assumingMemoryBound綁定指針為T(mén)類(lèi)型
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }

    }
}

struct HTStruct {
    var age = 18
    var name = "ht"
}

// 讀取將HTStuct指針內(nèi)容,賦值給StructMetadata  (unsafeBitCast: 通過(guò)字節(jié)讀取)
let p = unsafeBitCast(HTStruct.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)

// 讀取當(dāng)前name的內(nèi)容指針
let namePtr = p.pointee.description.pointee.name.get()
let count = p.pointee.description.pointee.numFields
print("類(lèi)型:\(String(cString: namePtr))") // name是CChar類(lèi)型旺遮,轉(zhuǎn)為字符串輸出
print("屬性個(gè)數(shù):\(count)")

// 單獨(dú)讀取第一個(gè)屬性名
var fields = p.pointee.description.pointee.fields.get()
let fieldRecord1Name = fields.pointee.fields.fieldName.get()
print(String(cString: fieldRecord1Name))

// 讀取所有記錄
print("----讀取所有屬性名----")
(0..<count).forEach { index in
    let recordPtr = withUnsafePointer(to: &fields.pointee.fields) {
        return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: FieldRecord.self).advanced(by: Int(index)))
    }
    print(String(cString: recordPtr.pointee.fieldName.get()))
}

print("----dump----")
dump(HTStruct()) // 相似的實(shí)現(xiàn)方式
  • 本文從struct結(jié)構(gòu)開(kāi)始入手分析赵讯,通過(guò)源碼。從初始化方法開(kāi)始耿眉,找到屬性類(lèi)型边翼、屬性個(gè)數(shù)屬性值鸣剪。成功還原相應(yīng)數(shù)據(jù)格式组底。初步窺探Mirror的內(nèi)部實(shí)現(xiàn)。

  • 源碼的世界西傀,都是大師級(jí)制作斤寇。每次拜讀和分析,都能學(xué)到一些思維方式拥褂。本文僅僅作為一個(gè)引路篇娘锁,更多有意思的東西,還需要我們自己慢慢探索饺鹃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末莫秆,一起剝皮案震驚了整個(gè)濱河市间雀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镊屎,老刑警劉巖惹挟,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異缝驳,居然都是意外死亡连锯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)用狱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)运怖,“玉大人,你說(shuō)我怎么就攤上這事夏伊∫≌梗” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵溺忧,是天一觀的道長(zhǎng)咏连。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鲁森,這世上最難降的妖魔是什么祟滴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮刀森,結(jié)果婚禮上踱启,老公的妹妹穿的比我還像新娘报账。我一直安慰自己研底,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布透罢。 她就那樣靜靜地躺著榜晦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪羽圃。 梳的紋絲不亂的頭發(fā)上乾胶,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音朽寞,去河邊找鬼识窿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛脑融,可吹牛的內(nèi)容都是我干的喻频。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肘迎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼甥温!你這毒婦竟也來(lái)了锻煌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤姻蚓,失蹤者是張志新(化名)和其女友劉穎宋梧,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體狰挡,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捂龄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了加叁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跺讯。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖殉农,靈堂內(nèi)的尸體忽然破棺而出刀脏,到底是詐尸還是另有隱情,我是刑警寧澤超凳,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布愈污,位于F島的核電站,受9級(jí)特大地震影響轮傍,放射性物質(zhì)發(fā)生泄漏暂雹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一创夜、第九天 我趴在偏房一處隱蔽的房頂上張望杭跪。 院中可真熱鬧,春花似錦驰吓、人聲如沸涧尿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)姑廉。三九已至,卻和暖如春翁涤,著一層夾襖步出監(jiān)牢的瞬間桥言,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工葵礼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留号阿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓鸳粉,卻偏偏與公主長(zhǎng)得像扔涧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赁严,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容