6、Mirror探索

177 阅读9分钟

一、AnyObject & .self、AnyClass & Any、self & Self、获取类型

1.1 AnyObject & .self

AnyObject代表任意类的实例类的类型仅类遵守的协议

class CTTeacher{
    var age = 18
}

var t = CTTeacher()
var t1:AnyObject = t.self
var t2:AnyObject = t.self.self.self.self
var t3:AnyObject = CTTeacher.self

print("end")

调试看一下x86源码: iShot2022-05-06_10.51.14.png iShot2022-05-06_11.02.43.png iShot2022-05-06_11.06.05.png

结论1:

T.self:当T实例对象时,T.self返回的就是实例对象本身;当T是类时,T.self返回的就是元类型

再来看看AnyObject仅类遵守的协议是啥意思

class CTTeacher{
    var age = 18
}

protocol MyProtocal:AnyObject {
}

extension CTTeacher:MyProtocal{}

iShot2022-05-07_09.37.36.png

但如果把class改为structiShot2022-05-07_09.43.29.png

结论2:

AnyObject:当协议继承自AnyObject时,这个协议只能够被class遵循,也就是仅类遵守的协议

1.2 AnyClass

AnyClass: 代表任意实例的类型【注意是类型,不是实例】

iShot2022-05-07_10.03.20.png 通过这个代码提示能看出,AnyClassAnyObject.Type类型的,具体来说:

class CTTeacher{
    var age = 18
}

var t:AnyClass = CTTeacher.self 

iShot2022-05-07_10.57.48.png

iShot2022-05-07_11.00.07.png

1.3 Any

Any: 代表任意类型,包括方法funcation类型、可选值optional类型

下面这个数组中的1,由于数组内容类型不同,所以需要用Any来指定数组类型,可以用任何类型,换成Anyobject就不行,因为1是int类型,基本类型不属于AnyObject类型。

var array:[Any] = [1, "CCT"]

iShot2022-05-07_09.58.11.png

AnyClass属于Any,Any代表了任意类型

class CTTeacher{
    var age = 18
}

var t:AnyClass = CTTeacher.self 

var s:Any = t

1.4 self & Self

self

class CTTeacher {
    var age = 18

    func test(){
        print(self) //这个self是指 实例对象
    }

    static func test1(){
        print(self) //这个self是指 类本身 也就是元类型
    }
}

let t = CTTeacher()
t.test()
CTTeacher.test1()

Self

用法一:作为方法返回值 指当前类的实例对象

class CTTeacher {
    func test() -> Self{ //Self作为方法返回值 指当前类的实例对象
        return self
    }
}

用法二:作为协议方法返回值 指遵循这个协议的[类]

protocol MyProtocol {
    func get() -> Self //Self作为协议方法返回值 指遵循这个协议的[类]
}

class CTPerson:MyProtocol{
    func get() -> Self {
        return self
    }
}

用法三:类中指代本类

class CTStudent{
    //类型属性
    static let age = 0
    //存储属性
    let age1 = age
    var age2 = age
    
    lazy var age3 = Self.age //Self指本类 不能写成self.age
}

1.5 获取类型

1.5.1 [ .self ] & [ .type ]

.type可以直接理解为:xx的类型

这儿有个逻辑容易搞混:

通过 xxx.self 得到 xxxx.type 类型

class CTTeacher{
    var age = 18
}

CTTeacher.self //这句代码得到:CTTeacher.type这个类型

1.5.2 type(of:)

type(of:) 获取一个值的动态类型

静态类型(static type) 是在编译时确定的,动态类型(dynamic type) 是在运行时确定的

var age = 10
var name = "CCT"

//value的静态类型是Any,也就是语法层面,我们指定的类型是Any
func test(_ value:Any){
    print("value:\(value),type:\(type(of: value))")
}

test(age)
test(name)

iShot2022-05-07_11.13.58.png

Self在使用的时候,返回的也是实际类型,我们可以理解为返回的就是type(of:) 实际类型

二、Swift Runtime

class LGTeacher {
    
    var age: Int = 18
    func teach(){
        print("teach")
    }
}

let t = LGTeacher()

func test(){
    var methodCount:UInt32 = 0
    let methodlist = class_copyMethodList(LGTeacher.self, &methodCount)
    for i in 0..<numericCast(methodCount) {
        if let method = methodlist?[i]{
            let methodName = method_getName(method);
            print("⽅法列表:\(String(describing: methodName))")
        }else{
            print("not found method");
        }
    }
    var count:UInt32 = 0
    let proList = class_copyPropertyList(LGTeacher.self, &count)
    for i in 0..<numericCast(count) {
        if let property = proList?[i]{
            let propertyName = property_getName(property);
            print("属性成员属性:\(String(utf8String: propertyName)!)")
        }else{
            print("没有找到你要的属性");
        }
    }
}

test()

1、运行一下,会发现什么都没打印。

结论1:对于纯 Swift 类来说,⽅法和属性不@objc修饰符的情况下,不具备Runtime特性

2、在属性和方法前加上@objc:

class LGTeacher {
    
    var age: Int = 18
    func teach(){
        print("teach")
    }
}

运行: iShot2022-05-07_15.14.42.png

结论2:对于纯 Swift 类,⽅法和属性前添加@objc标识,可以通过Runtime API拿到,但是在OC中是没法进⾏调度的

3、把LGTeacher继承自NSObject,去掉@objc标识:

class LGTeacher:NSObject {
    var age: Int = 18
    func teach(){
        print("teach")
    }
}

运行: iShot2022-05-07_15.21.18.png 只拿到init方法

再加上@objc标识:

class LGTeacher:NSObject {
    @objc var age: Int = 18
    @objc func teach(){
        print("teach")
    }
}

运行: iShot2022-05-07_15.22.49.png

结论3:对于继承⾃ NSObject 类来说,如果要通过Runtime API获取当前的属性和⽅法,必须在其声明前添加@objc关键字。

纯swift类没有动态性,但在⽅法、属性前添加dynamic修饰,可获得动态性。

继承⾃NSObject的swift类,其继承⾃⽗类的⽅法具有动态性,其它⾃定义⽅法、属性想要获得动态性,需要添加dynamic修饰。

若⽅法的参数、属性类型为swift特有、⽆法映射到objective-c的类型(如Character、Tuple),则此⽅法、属性⽆法添加dynamic修饰(编译器报错)

三、Mirror基本用法

构建⼀个Mirror实例,这⾥传⼊的参数是 Any,也就意味着当前可以是类,结构体,枚举

let mirror = Mirror(reflecting: <#Any#>)

class LGTeacher {
    
    var age: Int = 18
    func teach(){
        print("teach")
    }
}

let t = LGTeacher()

let mirror = Mirror(reflecting: t)

//遍历 children 属性,这是⼀个集合
for pro in mirror.children {

    //直接通过 label 输出当前的名称,value 输出当前反射的值
    print("\(pro.label) - \(pro.value)")
}

iShot2022-05-07_15.46.58.png

3.1 Mirror使用案例

protocol CTJsonMap{
    func jsonMap() -> Any
}

extension CTJsonMap{
    
    func jsonMap() -> Any {
        
        let mirror = Mirror(reflecting: self)
        
        guard !mirror.children.isEmpty else {
            return self
        }
        
        var result:[String: Any] = [:]
        
        for child in mirror.children {
            
            if let value = child.value as? CTJsonMap {
                
                if let key = child.label {
                    result[key] = value.jsonMap()
                }
                else{
                    print("No keys")
                }
            }
            else{
                print("value not conform CTJsonMap")
            }
        }
        return result
    }
}

//使用:
class CTTeacher {
    
    var age: Int = 18
    func teach(){
        print("teach")
    }
}

extension CTTeacher:CTJsonMap{}
extension String:CTJsonMap{}
extension Int:CTJsonMap{}


let t = CTTeacher()
print(t.jsonMap()) // ["age": 18]

3.2 错误抛出与捕获 throws & try

Swift 提供 Error协议 来标识当前应⽤程序发⽣错误的情况

结合上面的案例,自定义一个错误类型:[继承自Error的枚举]

//继承自Error
enum JSONMapError: Error{
    case emptyKey
    case notConformProtocol
}

修改部分代码: iShot2022-05-07_17.46.11.png iShot2022-05-07_17.48.20.png try try? try! iShot2022-05-07_17.49.20.png

四、Mirror源码解析

4.1 Mirror源码

源代码Mirror.swift,定位到初始化的方法

这⾥接受⼀个Any类型的参数,这⾥判断subject是否遵循了自定义协议customReflectable协议,如果是,就直接调⽤customMirror, 否则就调用Mirror默认的初始化方法Mirror(internalReflecting: subject)iShot2022-05-12_11.11.18.png

4.2 CustomReflectable协议

class CTTeacher: CustomReflectable {
    var age: Int
    var name: String
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    var customMirror: Mirror{
        let info = KeyValuePairs<String, Any>.init(dictionaryLiteral: ("age", age),("name", name))
        let mirror = Mirror.init(self, children: info, displayStyle: .class, ancestorRepresentation: .generated)
        return mirror
    }
}

CustomReflectable协议:这个协议只有一个属性 customMirror,返回的就是一个Mirror实例,它能让我们自定义反射的信息 iShot2022-05-12_11.20.52.png iShot2022-05-12_11.39.34.png 对比一下不遵循CustomReflectable协议的: iShot2022-05-12_11.46.53.png

结论:遵循了CustomReflectable协议,我们可以自定义反射的信息,在LLDBpo调试的时候,会输出对象的内存地址和反射出的属性及属性值

注意初始化方法里面的 displayStyle: 要根据实际传入对应的值 iShot2022-05-12_11.44.29.png

回到Mirror的初始化方法,我们全局搜一下Mirror(internalReflecting这个初始化方法,来到ReflectionMirror.swift文件 iShot_2022-05-24_15.09.47.png

看看 _getNormalizedType(),以及 _getChildCount()以及这两个方法的定义: iShot_2022-05-24_15.11.59.png

发现 @_silgen_name 这个神奇玩意儿,并且俩方法都没有具体实现。

sigen_name()

@_silgen_name是⼀个编译器字段,是Swift的⼀个隐藏符号,作⽤是将某个C/C++语⾔函数直接映射为Swift函数。

比如在C中定义一个函数,如果想在Siwft中使用这个函数,那么先在c文件中定义这个函数: iShot_2022-05-24_15.30.28.png

然后在Swift中使用@_silgen_name关键字:

iShot_2022-05-24_15.32.40.png

回到_getNormalizedType()以及 _getChildCount()方法,那么也就是说,调用这俩函数实际上调用的是其对应的c函数。swift_reflectionMirror_normalizedType swift_reflectionMirror_count

全局搜swift_reflectionMirror_normalizedType,可以在ReflectionMirror.mm文件中找到 iShot_2022-05-24_15.39.58.png

我们再找到call方法 iShot_2022-05-24_15.56.46.png

这⾥是⼀个回调函数,回调的具体数据都是由ReflectionMirrorImpl结构体实现 iShot_2022-05-24_15.57.46.png

下面有一个类型的匹配判断,调用上面那个auto call()方法,传入对应类型的impl iShot_2022-05-24_16.02.32.png

ReflectionMirrorImpl结构体是一个抽象类,意味着不同类型的反射都需要去实现。 iShot_2022-05-24_15.58.34.png

那么class, struct, enum, Tuple它们都有对应的具体实现,拿struct为例子具体看看Mirror是如何获取这些数据的: iShot_2022-05-24_16.07.05.png iShot_2022-05-24_16.08.46.png

是否可以反射:通过Metadata中的getDescription查询字段isReflectable iShot_2022-05-24_16.12.09.png

获取属性数量:通过Metadata中的getDescription查询字段NumFields iShot_2022-05-24_16.10.07.png

获取具体属性: iShot_2022-05-24_16.14.08.png

可以看到是这⾥通篇都是通过 MetadatagetDescription()FieldDescrition 这⼏个东⻄来去实现的,⼀个是当前类型的元数据,⼀个是当前类型的描述,⼀个是对当前类型属性的 描述。

在Swift源码Metadata.h中找到TargetEnumMetadata iShot_2022-05-30_09.54.22.png 可以看到它继承于TargetValueMetadata,点进去: iShot_2022-05-30_09.55.09.png 它有一个Description字段,是TargetValueTypeDescriptor类型。 它继承自TargetMetadata,再点进去: iShot_2022-05-30_09.58.06.png 有一个kind类型。

ok,到这里我们可以推断出EnumMetadata在Swift中长这样:

struct TargetEnumMetadata{

    var kind: Int
    var typeDescriptor: UnsafeRawPointer //其中这⾥指向的是descriptor
}

接下来可以通过追踪Descriptor的继承链,来还原它的结构: iShot_2022-05-30_10.08.17.png

struct TargetEnumDescriptor{
    var flags: Int32
    var parent: TargetRelativeDirectPointer<UnsafeRawPointer>
    var name: TargetRelativeDirectPointer<CChar>
    var accessFunctionPointer: TargetRelativeDirectPointer<UnsafeRawPointer>
    var fieldDescriptor: TargetRelativeDirectPointer<UnsafeRawPointer>
    var NumPayloadCasesAndPayloadSizeOffset: UInt32
    var NumEmptyCases: UInt32
}

那么这里面有好几个属性是TargetRelativeDirectPointer这个类型的,它是什么玩意儿?

点击进去可以发现,它是一个模板类,接收三个参数,⼀个是 Runtime , ⼀个是Pointee , 一个是Bool 类型默认为 True。 iShot_2022-05-30_10.18.17.png 点击RelativeDirectPointer进去看: iShot_2022-05-30_10.22.44.png 这里的T就是我们进来的类型,Offset就是int32_t类型。在swift中引用一个Object有两种情况:

  1. 当前 Object 的引⽤是⼀个 absolute 指针,就像我们的对象⼀样 iShot_2022-05-30_10.40.32.png
  2. 当前指针存储的是相对引⽤,相对引⽤指的是当前引⽤的内存地址到当前对象的内存地址的距离 iShot_2022-05-30_10.40.46.png

OK,在TargetEnumDescriptor中的TargetRelativeDirectPointer的属性类型存的都是相对地址信息。 我们尝试用swift还原TargetRelativeDirectPointer

struct TargetRelativeDirectPointer<Pointee>{
    var offset: Int32
    mutating func getmeasureRelativeOffset() -> UnsafeMutablePointer<Pointee>{
        let offset = self.offset
        return withUnsafePointer(to: &self) { p in
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self))
        }
    }
}

我们把TargetRelativeDirectPointer的定义和以上代码结合起来看:

iShot_2022-05-30_11.17.25.png

以上都串起来:

struct TargetRelativeDirectPointer<Pointee>{
    var offset: Int32
    mutating func getmeasureRelativeOffset() -> UnsafeMutablePointer<Pointee>{
        let offset = self.offset
        return withUnsafePointer(to: &self) { p in
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self))
        }
    }
}

struct TargetEnumDescriptor{
    var flags: Int32
    var parent: TargetRelativeDirectPointer<UnsafeRawPointer>
    var name: TargetRelativeDirectPointer<CChar>
    var accessFunctionPointer: TargetRelativeDirectPointer<UnsafeRawPointer>
    var fieldDescriptor: TargetRelativeDirectPointer<UnsafeRawPointer>
    var NumPayloadCasesAndPayloadSizeOffset: UInt32
    var NumEmptyCases: UInt32
}

struct TargetEnumMetadata{
    var kind: Int
    var typeDescriptor: UnsafeMutablePointer<TargetEnumDescriptor>
}

来个demo,跑一下:

enum TerminalChar {
  case Plain(Bool)
  case Bold
  case Empty
  case Cursor
}

var e = TerminalChar.self

let ptr = unsafeBitCast(TerminalChar.self as Any.Type, to: UnsafeMutablePointer<TargetEnumMetadata>.self)

let namePtr = ptr.pointee.typeDescriptor.pointee.name.getmeasureRelativeOffset()

print(String(cString: namePtr))
print(ptr.pointee.typeDescriptor.pointee.NumPayloadCasesAndPayloadSizeOffset)
print(ptr.pointee.typeDescriptor.pointee.NumEmptyCases)

这里面unsafeBitCast解释一下:

var age = 10
var age1 = unsafeBitCast(age, to: Double.self)
print(age1)

iShot_2022-05-30_11.48.09.png

ok,所以上面demo的代码就是:(它俩本质上是一样的类型,所以这么转没问题) iShot_2022-05-30_11.56.38.png iShot_2022-05-30_14.02.37.png

再来看一下Class:

classMetadata之前分析过,直接拿来用。然后它的typeDescriptor也还原为TargetClassDescriptor

struct TargetClassMetadata{
    var kind: Int
    var superClass: Any.Type
    var cacheData: (Int, Int)
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDescriptor: UnsafeMutablePointer<TargetClassDescriptor>
    var iVarDestroyer: UnsafeRawPointer
}

struct TargetClassDescriptor{
    var flags: Int32
    var parent: Int32
    var name: TargetRelativeDirectPointer<CChar>
    var accessFunctionPointer: TargetRelativeDirectPointer<UnsafeRawPointer>
    var fieldDescriptor: TargetRelativeDirectPointer<FieldDescriptor>
    var superClassType: TargetRelativeDirectPointer<CChar>
    var metadataNegativeSizeInWords: Int32
    var metadataPositiveSizeInWords: Int32
    var numImmediateMembers: Int32
    var numFields: Int32
    var fieldOffsetVectorOffset: Int32 //每一个属性值距离当前实例对象地址的偏移量

    func getFieldOffsets(_ metadata: UnsafeRawPointer) -> UnsafePointer<Int>{
      return metadata.assumingMemoryBound(to: Int.self).advanced(by: numericCast(self.fieldOffsetVectorOffset))
    }

    var genericArgumentOffset: Int {
        return 2
    }
}

来个demo跑一下:

class LGTeacher{
    var age: Int = 18
    var name: String = "Kody"
}

var t = LGTeacher()

let ptr = unsafeBitCast(LGTeacher.self as Any.Type, to: UnsafeMutablePointer<TargetClassMetadata>.self)

let namePtr = ptr.pointee.typeDescriptor.pointee.name.getmeasureRelativeOffset()
print("current class name: \(String(cString: namePtr))")

let numFileds = ptr.pointee.typeDescriptor.pointee.numFields
print("当前类属性的个数: \(numFileds)")

let superClassType = ptr.pointee.typeDescriptor.pointee.superClassType.getmeasureRelativeOffset()
print("current superClassType: \(String(cString: superClassType))")


let offsets = ptr.pointee.typeDescriptor.pointee.getFieldOffsets(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self))

//print("======= start fetch filed ======")

for i in 0..<numFileds{

    //取得字段名
    let fieldDespritor = ptr.pointee.typeDescriptor.pointee.fieldDescriptor.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.fieldName.getmeasureRelativeOffset()
    print("--- filed: \(String(cString: fieldDespritor)) info begin ----")

    let fieldOffset = offsets[Int(i)]
    
    //取得属性名
    //Int ,String
    let typeMangleName = ptr.pointee.typeDescriptor.pointee.fieldDescriptor.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.mangledTypeName.getmeasureRelativeOffset()
    print("typeManglename:\(typeMangleName)")


    let genericVector = UnsafeRawPointer(ptr).advanced(by: ptr.pointee.typeDescriptor.pointee.genericArgumentOffset * MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: Any.Type.self)

    //HandJSON
    // swift_getTypeByMangledNameInContext 是c函数,要使用它有两种方法:
    // 1是 @_silgen_name("swift_getTypeByMangledNameInContext") 符号映射信息
    // 2是 直接在c++头文件中声明这个方法名
    let fieldType = swift_getTypeByMangledNameInContext(
        typeMangleName,
        256,
        UnsafeRawPointer(ptr.pointee.typeDescriptor),
        UnsafeRawPointer(genericVector)?.assumingMemoryBound(to: Optional<UnsafeRawPointer>.self))

//    print(fieldType as Any)

    //比较难理解,HandJSON
    let type = unsafeBitCast(fieldType, to: Any.Type.self)

    print(type)

    let instanceAddress = Unmanaged.passUnretained(t as AnyObject).toOpaque()

    let value = customCast(type: type)

    print("fieldType:\(type) \nfieldValue: \(value.get(from: instanceAddress.advanced(by: fieldOffset))) ")
//
//    print("--- filed: \(String(cString: fieldDespritor)) info end ---- \n")
}
protocol BrigeProtocol {}

extension BrigeProtocol {
    static func get(from pointer: UnsafeRawPointer) -> Any {
        pointer.assumingMemoryBound(to: Self.self).pointee
    }
}

//协议的Metadata:
//协议见证表
struct BrigeProtocolMetadata {
    let type: Any.Type
    let witness: Int
}

func customCast(type: Any.Type) -> BrigeProtocol.Type {
    var container = BrigeProtocolMetadata(type: type, witness: 0)
    var protocolType = BrigeProtocol.Type.self
    var cast = unsafeBitCast(container, to: BrigeProtocol.Type.self)
    return cast
}