swift进阶十:Mirror源码解析

swift进阶 学习大纲

上一节,我们简单体验Mirror反射机制。

本节,通过源码,深入探索Mirror反射 💪

  1. 初始化
  2. 属性类型
  3. 属性个数
  4. 属性值

  • Mirror(反射):
    可以动态获取类型成员变量,在运行时可以调用方法属性等行为的特性

回顾Mirror简单使用

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)")
}
  • 打印结果:
image.png

1. 初始化

我们知道,OC中通过Runtime能轻松获取属性方法协议等,但swiftMirror是如何获取到属性类型个数的呢?

  • 带着这些疑问,我们在swift源码中搜索Mirror.swift

    image.png

  • 我们搜索找到初始化方法public init(reflecting subject: Any),从这里开始我们的探索:

    image.png

  • 首先,我们分析一下如何通过subject获取类型

2. 类型获取

  • 搜索_getNormalizedType:
    image.png

注意:

  • 源码分析时,首先分析结构,结构是由继承链上的所有属性共同决定的。

  • 函数只是所有属性服务的。所以我们的重心,就是先找到结构,尝试重写结构,能使用自己的结构完整读取到相应的内容证明源码分析的结果是正确的。

  • 在这个过程,顺道看一眼函数,在属性操作时,回头分析哪些函数操作了它,是如何实现的。
    这样才能完整梳理清楚。(当然,得花大量时间反复研究才行)

  • 根据源码探索,以struct类型为例,可知structMetaData结构为:
image.png
  • 按照structMetadata格式,尝试读取struct的类型:
    (get:仿照源码中RelativeDirectPointer (相对位置指针)进行偏移,获取真实内存值)
struct StructMetadata {
    var kind:        Int
    var description: UnsafeMutablePointer<StructMetadataDesc>
}

struct StructMetadataDesc {
    var flags:              UInt32
    var parent:             UInt32  // 展示用Uint32代替,实际是相同大小的结构体,
    var name:               RelativeDirectPointer<CChar>
    // . . .  (当前研究获取属性类型,后面的属性先不管)
}

struct RelativeDirectPointer<T>{
    var offset: Int32
    
    // 偏移offset位置,获取内容指针
    mutating func get() -> UnsafeMutablePointer<T> {
        let offset = self.offset
        // withUnsafePointer获取指针
        return withUnsafePointer(to: &self) { p in
            // UnsafeMutablePointer 返回T类型对象的指针
            // UnsafeRawPointer将p指针转换为未知类型
            // numericCast将offset转换为偏移单位数
            // advanced进行内存偏移
            // assumingMemoryBound绑定指针为T类型
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }
        
    }
}

struct HTStruct {
    var age = 18
}

// 读取将HTStuct指针内容,赋值给StructMetadata  (unsafeBitCast: 通过字节读取)
let p = unsafeBitCast(HTStruct.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)

// 读取当前name的内容指针
let namePtr = p.pointee.description.pointee.name.get()

// name是CChar类型,转为字符串输出
print(String(cString: namePtr))
  • 成功获取HTStruct类型:
    image.png

3. 属性个数

  • 回到初始化方法,可以看到是通过_getChildCount获取属性个数:

    image.png

  • 仿写代码,

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

struct StructMetadataDesc {
    var flags:                   UInt32
    var parent:                  UInt32  // 展示用Uint32代替,实际是相同大小的结构体,
    var name:                    RelativeDirectPointer<CChar>  // 不在乎具体类型,就先用UnsafeRawPointer
    var accessFunctionPtr:       RelativeDirectPointer<UnsafeRawPointer>  // 不在乎具体类型,就先用UnsafeRawPointer
    var fields:                  RelativeDirectPointer<UnsafeRawPointer>  // 不在乎具体类型,就先用UnsafeRawPointer
    var numFields:               UInt32  // 属性个数
    var fieldOffsetVectorOffset: UInt32
}

struct RelativeDirectPointer<T>{
    var offset: Int32

    // 偏移offset位置,获取内容指针
    mutating func get() -> UnsafeMutablePointer<T> {
        let offset = self.offset
        // withUnsafePointer获取指针
        return withUnsafePointer(to: &self) { p in
            // UnsafeMutablePointer 返回T类型对象的指针
            // UnsafeRawPointer将p指针转换为未知类型
            // numericCast将offset转换为偏移单位数
            // advanced进行内存偏移
            // assumingMemoryBound绑定指针为T类型
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }

    }
}

struct HTStruct {
    var age = 18
}

// 读取将HTStuct指针内容,赋值给StructMetadata  (unsafeBitCast: 通过字节读取)
let p = unsafeBitCast(HTStruct.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)

// 读取当前name的内容指针
let namePtr = p.pointee.description.pointee.name.get()
let count = p.pointee.description.pointee.numFields

// name是CChar类型,转为字符串输出
print(String(cString: namePtr))
print("HTStruct属性个数:\(count)")
  • 打印结果:


    image.png
  • 修改结构体属性个数后:

    image.png

成功的重写结构,拿到属性个数,下面,我们来分析属性值的获取

4. 属性值

  • 属性值的获取,也是在children获取时得到。我们循着路径,找到对应类型的impl,读取description里面的fields,分析fields内部结构,发现每个属性是以FieldRecord格式进行存储。 存放的位置在fields的最后。有几个属性就会新增几条FieldRecord记录,可通过内存地址偏移获取所有属性名
image.png
// 结构体类型
struct StructMetadata {
    var kind:        Int
    var description: UnsafeMutablePointer<StructMetadataDesc>
}

// 结构体类型的描述
struct StructMetadataDesc {
    var flags:                   UInt32
    var parent:                  UInt32  // 展示用Uint32代替,实际是相同大小的结构体,
    var name:                    RelativeDirectPointer<CChar>  // 不在乎具体类型,就先用UnsafeRawPointer
    var accessFunctionPtr:       RelativeDirectPointer<UnsafeRawPointer>  // 不在乎具体类型,就先用UnsafeRawPointer
    var fields:                  RelativeDirectPointer<FieldDescription>  // 记录所有属性内容
    var numFields:               UInt32   // 属性个数
    var fieldOffsetVectorOffset: UInt32
}

// 记录结构体内所有属性的结构
struct FieldDescription {
    var MangledTypeName:         RelativeDirectPointer<CChar>
    var Superclass:              RelativeDirectPointer<CChar>
    var Kind:                    UInt16
    var FieldRecordSize:         UInt16
    var NumFields:               UInt32
    var fields:                  FieldRecord // 连续存储空间 (有几个数据,就会在后面添加几个记录,通过内存平移读取)
}

// 每个属性的内容
struct FieldRecord {
    var flag:                    Int32
    var mangledTypeName:         RelativeDirectPointer<CChar>
    var fieldName:               RelativeDirectPointer<CChar>   // 属性名称
}

// 相对位移指针
struct RelativeDirectPointer<T>{
    var offset: Int32

    // 偏移offset位置,获取内容指针
    mutating func get() -> UnsafeMutablePointer<T> {
        let offset = self.offset
        // withUnsafePointer获取指针
        return withUnsafePointer(to: &self) { p in
            // UnsafeMutablePointer 返回T类型对象的指针
            // UnsafeRawPointer将p指针转换为未知类型
            // numericCast将offset转换为偏移单位数
            // advanced进行内存偏移
            // assumingMemoryBound绑定指针为T类型
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }

    }
}

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

// 读取将HTStuct指针内容,赋值给StructMetadata  (unsafeBitCast: 通过字节读取)
let p = unsafeBitCast(HTStruct.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)

// 读取当前name的内容指针
let namePtr = p.pointee.description.pointee.name.get()
let count = p.pointee.description.pointee.numFields
print("类型:\(String(cString: namePtr))") // name是CChar类型,转为字符串输出
print("属性个数:\(count)")

// 单独读取第一个属性名
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()) // 相似的实现方式
  • 本文从struct结构开始入手分析,通过源码。从初始化方法开始,找到属性类型属性个数属性值。成功还原相应数据格式。初步窥探Mirror的内部实现。

  • 源码的世界,都是大师级制作。每次拜读和分析,都能到一些思维方式。本文仅仅作为一个引路篇,更多有意思的东西,还需要我们自己慢慢探索。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容