iOS-Swift 独孤九剑:六、元类型、Mirror、HandyJson

1,280 阅读12分钟

一、元类型

1. Any 和 AnyObject

Swift 提供了 2 中特殊的类型:AnyAnyObjectAny:可以代表任意类型(枚举、结构体、类,也包括函数类型)。AnyObject:可以代表任意类型。

在协议后面写上: AnyObject 代表只有类才能遵守这个协议,在协议后面写上 class,也代表只有类才能遵守这个协议。如图:

协议后面加上AnyObject.png

2. is 和 as

接下来我先声明一些类和协议,代码如下:

protocol SHRunnable {
    func run()
}

class SHPerson { }

class SHStudent: SHPerson, SHRunnable {
    func run() {
        print("run")
    }

    func study() {
        print("study")
    }
}

is 是用来判断是否为某种类型的:

var stu: Any = SHStudent()
print(stu is SHStudent) // true
print(stu is SHPerson)  // true
print(stu is SHRunnable)// true

stu = 10
print(stu is SHStudent) // false

stu = "Coder_张三"
print(stu is String)    // true

as 是用来做强制类型转换的:

var stu: Any = 10
// 括号里的会转换成 SHStudent? 类型
(stu as? SHStudent)?.run()

stu = SHStudent()
(stu as? SHStudent)?.study()   // study

在使用 as 进行强制类型转换的时候,我们可能需要在 as 后面加个问号(?)。如上代码,编译器会把它转换成一个可选 SHStudent,所以在调用 run 的时候需要在前面加上问号(?)。

除了可以使用问号(?)之外,我们还可以使用感叹号(!)。代码如下:

(stu as! SHStudent).study()

使用感叹号(!)就代表着对可选的 SHStudent 进行强制解包,最终强制转换的结果就是 SHStudent 类型,所以不必在调用方法的时候前面加上问号(?)。

那什么情况下不用加问号(?)和或者感叹号(!)呢,就是百分之百能够强制转换成功的。举个例子:

var d = 10 as Double
// 或者
var data = [Any]()
data.append(Int("123") as Any)

3. T.self,T.Type,AnyClass

T.self: T 是实例对象,当前 T.self 返回的就是实例对象本身。如果 T 是类,当前 T.self 返回的就是元类型。

什么意思呢,代码如下:

class SHPerson {
    var age: Int = 18
    var name: String = "Coder_张三"
}

let p = SHPerson()
let p_self = p.self
print("end")

将断点打在 print 处,然后格式化输出一下 pp_self 的内存,如图:

p和p.self.png

接下来我们把 p.self 换成 SHPerson.self,再来格式化输出,如图:

p和SHPerson.self.png

所以,在 T.self 中,当 T 为实例对象的时候,T.self 返回的是实例对象本身。当 T 为类的时候,T.self 返回的是一个元类型,也就是《结构体与类》这篇文章讲的元数据(metadata)。

T.self 属于 T.Type 类型,怎么去理解呢,我们来看下面这一段的代码:

var p: SHPerson = SHPerson()
var p_type: SHPerson.Type = SHPerson.self

如上代码,我们也可以这么去理解 SHPerson.Type。首先,变量 p 的类型是 SHPerson。那么变量 p_type 是的类型是 SHPerson.Type

接下来我们介绍一个叫 AnyClass 的东西,我们来看一下它的定义:

public typealias AnyClass = AnyObject.Type

通过定义我们知道,它是 AnyObject.Type 类型的别名,AnyObject 代表的是任意类类型,我们看一段代码:

let objcType: AnyObject.Type = SHPerson.self

AnyClass 既然是 AnyObject.Type 的别名,我们也可以使用 AnyClass 代替 AnyObject.Type,代码如下:

let objcType: AnyClass = SHPerson.self

4. Self

4.1 type(of:)

我们先来了解一个函数 - type(of:)type(of:) 的作用是传一个值,返回这个值的动态类型。请看下面的代码:

func printInfo(_ value: Any) {
    let t = type(of: value)
    print("'\(value)' of type '\(t)'") // '5' of type 'Int'
}

let count: Int = 5
printInfo(count)

printInfo(:) 函数的参数是一个 Any 类型,我在调用 printInfo(:) 函数的时候传一个 Int 类型的值。此时,在函数内部,拿到 value 的类型为 Any 类型,那要知道 value 的具体类型,可以通过 type(of:) 函数拿到具体的类型。

printInfo(:) 函数中,value 在语法层的类型为 Any 类型,这个叫静态类型。而我此时传过去的值为 Int 类型,这个是 value动态类型type(of:) 的具体介绍可以去看苹果的官方文档。

4.2 Self 的用法

接下来我们来了解 SelfSelf 代表当前类型,一般用作返回值的类型,限定返回值跟方法调用者必须是同一类型(也可以作为参数类型)。代码如下:

protocol SHProtocol {
    func mySelf() -> Self
    func printSelf(_ mySelf: Self)
}

class SHPerson: SHProtocol {
    func mySelf() -> Self {
        return self
    }
    func printSelf(_ mySelf: SHPerson) {
        print(mySelf)
    }
}

let p = SHPerson()
print(p.mySelf())       // 工程名.SHPerson
p.printSelf(p.mySelf()) // 工程名.SHPerson

二、Mirror

1. 初识 Mirror

通过名称可直接翻译为镜子,它是任何类型的实例的子结构和显示样式的表示。镜子描述了构成特定实例的部分,例如实例的存储属性、集合或元组元素,或其活动枚举案例。镜子还提供了一个“显示样式”属性,用于建议如何渲染该镜子。

Swift 的反射机制就是基于结构体 Mirror 实现的,所谓的反射机制就是可以动态获取类型、成员信息,在运⾏时可以调⽤⽅法、属性等⾏为的特性

在使⽤ OC 开发时很少强调其反射概念,因为 OC 的 Runtime 要⽐其他语⾔中的反射强⼤的多。但是 Swift 是⼀⻔类型 安全的语⾔,不⽀持我们像 OC 那样直接操作,它的标准库仍然提供了反射机制来让我们访问成员信息。

我们接下来看 Mirror 的初始化方法,如下:

/// 创建一个反映给定实例的镜子。
/// subject:为其创建镜像的实例。
public init(reflecting subject: Any)

Mirror 有一个属性 children,我们可以通过它来拿到反射的实例的相关信息。其定义如下:

public let children: Mirror.Children

它的类型为 Mirror.Children,这个类型其实是一个 AnyCollection<Mirror.Child> 类型的别名。而其中的 Mirror.Child 是一个元组类型,我们来看一下他们的定义:

public typealias Child = (label: String?, value: Any)

public typealias Children = AnyCollection<Mirror.Child>

它还有更多的属性,我们来看下面三个:

// 反射对象的类型
public let subjectType: Any.Type
// 反射的类型
public let displayStyle: Mirror.DisplayStyle?
// 对象父类的镜子
public var superclassMirror: Mirror? { get }

// Other properties and methods
// ......

接下来我们创建一个 Mirror 的实例,通过 Mirror 的实例遍历出类的属性信息,代码如下:

class SHPerson {
var age = 0
var name = ""
var weight = 0.0
var height = 0.0
}

let p = SHPerson()
p.age = 18
p.name = "Coder_张三"
p.weight = 60
p.height = 180

// 传一个 Any 类型,返回这个类型的 Mirror。
let p_mirror = Mirror(reflecting: p)
for child in p_mirror.children {
print(child.label ?? "",":",child.value)
}
打印结果:
age : 18
name : Coder_张三
weight : 60.0
height : 180.0

成功打印出对象的属性名称和属性的值。

2. Mirror 源码探究

接下来我们将对 Mirror 的实现做一个了解,在源码中找到 Mirror.swift,快速定位到 Mirror 的初始化方法,如下:

public init(reflecting subject: Any) {
    if case let customized as CustomReflectable = subject {
        self = customized.customMirror
    } else {
        self = Mirror(internalReflecting: subject)
    }
}

这个初始化函数中判断了 subject 是否遵守 CustomReflectable 协议,遵守 CustomReflectable 协议的实例,直接调用 customMirror 属性。通过名称,大概猜得到,遵守了 CustomReflectable 协议的实例,返回的 Mirror 是需要自定义的。如果没有遵守 CustomReflectable 协议,直接走正常流程,进行下级的函数调用。

下面是我用 po p 打印出实例 p 的内容,打印出来的是实例 p 的地址。

po 打印实例 p.png

但这个时候我想把实例 p 的结构打印出来,可以通过遵守 CustomReflectable 协议,代码如下:

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

重新 po p 之后打印出了 SHPerson 的属性的信息。

CustomReflectable 之后的po.png

接下来回到源码中,全局搜索 (internalReflecting,定位到 ReflectionMirror.swift ⽂件,在 Mirrorextension 中找到了这个初始化方法,其实现如下:

internal init(internalReflecting subject: Any,
                                 subjectType: Any.Type? = nil,
                                 customAncestor: Mirror? = nil)
{
    // 1. 判断实例的类型是否为 nil,如果为 nil,获取实例的类型 - T.Type
    let subjectType = subjectType ?? _getNormalizedType(subject, type: type(of: subject))

    // 2. 通过 map 获取属性信息
    let childCount = _getChildCount(subject, type: subjectType)
    let children = (0 ..< childCount).lazy.map({
        getChild(of: subject, type: subjectType, index: $0)
    })
    self.children = Children(children)

    // 3. 获取父类的 Mirror
    self._makeSuperclassMirror = {
        guard let subjectClass = subjectType as? AnyClass,
            let superclass = _getSuperclass(subjectClass) else {
                return nil
        }

        // Handle custom ancestors. If we've hit the custom ancestor's subject type,
        // or descendants are suppressed, return it. Otherwise continue reflecting.
        if let customAncestor = customAncestor {
            if superclass == customAncestor.subjectType {
                return customAncestor
            }
            if customAncestor._defaultDescendantRepresentation == .suppressed {
                return customAncestor
            }
        }
        return Mirror(internalReflecting: subject,
                             subjectType: superclass,
                             customAncestor: customAncestor)
    }
    // 4. 设置 displayStyle
    let rawDisplayStyle = _getDisplayStyle(subject)
    switch UnicodeScalar(Int(rawDisplayStyle)) {
        case "c": self.displayStyle = .class
        case "e": self.displayStyle = .enum
        case "s": self.displayStyle = .struct
        case "t": self.displayStyle = .tuple
        case "\0": self.displayStyle = nil
        default: preconditionFailure("Unknown raw display style '\(rawDisplayStyle)'")
    }
    // 5. 设置 subjectType 和 _defaultDescendantRepresentation
    self.subjectType = subjectType
    self._defaultDescendantRepresentation = .generated
}

我们看到第一行代码,这一行是获取 subject 的类型,通过调用 _getNormalizedType 函数来获取,我们来看一下 _getNormalizedType 函数:

@_silgen_name("swift_reflectionMirror_normalizedType")
internal func _getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type

注意看,函数上面使用了一个编译字段 @_silgen_name,这个是 Swift 的一个隐藏符号,作⽤是将某个 C/C++ 函数直接映射为 Swift 函数。也就是我们在调用 _getNormalizedType 函数的时候,本质上是在调用 swift_reflectionMirror_normalizedType 函数。

我们来看 swift_reflectionMirror_normalizedType 的实现:

// func _getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
const Metadata *swift_reflectionMirror_normalizedType(OpaqueValue *value,
                                                      const Metadata *type,
                                                      const Metadata *T) {
    return call(value, T, type, [](ReflectionMirrorImpl *impl) { return impl->type; });
}

函数的实现在 ReflectionMirror.cpp 文件,它调用了一个 call 函数,这个 call 函数有一个回调函数,可以理解为 Swift 的闭包。可以通过 call 函数返回不同的类型,我们来看 call 函数的其他调用,如下:

call 函数的调用.png

其实在很多地方都调用了 call 函数,这里只截图了两个,感兴趣的可以去源码看看,我们接下来看一下 call 函数的实现, call 函数的实现比较长,我先贴出一部分:

call 函数的实现.png

图中的类的调用和非类的调用是什么意思呢,比如非类的调用,在回调函数里面将 typevalue 赋值给 impl 对应的成员。那这个 impl 的类型是一个 ReflectionMirrorImpl 的类型,ReflectionMirrorImpl 是一个模版类。

我们继续来看 call 函数剩下的实现,如下:

call 函数的实现2.png

它通过 kind 来判断实例的类型,从而拿到不同类型对应的 impl,调用非类的 call(&impl)。而类是调用 callClass

我们选一个 impl 来看,比如枚举 - EnumImpl,如图:

EnumImpl.png

通过 isReflectable 函数来判断是否接受反射,注意看,它的实现无非就是拿到 Description,通过 DescriptionisReflectable 函数来判断的。在获取 count 的时候用到了 isReflectable 函数,代码如下:

intptr_t count() override {
    if (!isReflectable()) {
        return 0;
    }

    // No fields if reflecting the enumeration type instead of a case
    if (!value) {
        return 0;
    }

    const Metadata *payloadType;
    getInfo(nullptr, &payloadType, nullptr);
    return (payloadType != nullptr) ? 1 : 0;
}

如果不支持反射,直接返回 0,否则走到下面,调用了一个 getInfo 函数。那 getInfo 函数又是什么呢,它的实现如下:

const char *getInfo(unsigned *tagPtr = nullptr,
                    const Metadata **payloadTypePtr = nullptr,
                    bool *indirectPtr = nullptr) {
    // 'tag' is in the range [0..NumElements-1].
    unsigned tag = type->vw_getEnumTag(value);

    StringRef name;
    FieldType info;
    std::tie(name, info) = getFieldAt(type, tag);
    const Metadata *payloadType = info.getType();
    bool indirect = info.isIndirect();

    if (tagPtr)
    *tagPtr = tag;
    if (payloadTypePtr)
    *payloadTypePtr = payloadType;
    if (indirectPtr)
    *indirectPtr = indirect;

    return name.data();
}

我直接找到关键的代码:

std::tie(name, info) = getFieldAt(type, tag);

getFieldAt 函数的实现有一部分是这么写的:

getFieldAt 的实现.png

这里拿到 Description,通过 Description 拿到 Fields,而 Fields 里面包含了属性信息,既然拿到了 Fields,就可以拿到属性相关的信息了。

三、TargetEnumMetadata 源码探索

1. 还原 TargetEnumMetadata 的结构

在源码中 TargetEnumMetadata 继承自 TargetValueMetadata,在 TargetValueMetadata 中,有一个成员变量 Description

/// An out-of-line description of the type.
TargetSignedPointer<Runtime, const TargetValueTypeDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;

TargetValueMetadata 又继承自 TargetMetadata,那么在 TargetMetadata 中,有一个成员变量 Kind

/// The kind. Only valid for non-class metadata; getKind() must be used to get
/// the kind value.
StoredPointer Kind;

此时,TargetEnumMetadata 的结构我们就可以还原出来了,代码如下:

struct TargetEnumMetadata {
    var Kind: Int
    var Description: UnsafeRawPointer
}

2 还原 TargetEnumDescriptor 和 TargetRelativeDirectPointer 的结构

此时,我们还需要将 Description 的结构还原出来,虽然 Description 在定义的时候是 TargetValueTypeDescriptor 类型的,但是在 TargetEnumMetadata 中应该是 TargetEnumDescriptor 类型的。因为在 TargetEnumMetadata 的源码结构中,获取 Description 的方法是这样的:

const TargetEnumDescriptor<Runtime> *getDescription() const {
    return llvm::cast<TargetEnumDescriptor<Runtime>>(this->Description);
}

所以,我们要将 TargetEnumMetadataDescription 还原的话需要从 TargetEnumDescriptor 入手。这里就不一一的去还原了,但需要注意,在《结构体与类》这篇文章中,我们去还原类的 Description 的时候,成员变量的类型往往都是用 Int32 或者 UnsafeRawPointer 来表示。那在我们直接将其成员的类型也给还原出来。

我们找到 TargetTypeContextDescriptor,这个类是 TargetEnumDescriptor 父类的父类,我拿其中的一个成员变量来看,如下:

/// The name of the type.
TargetRelativeDirectPointer<Runtime, const char, /*nullable*/ false> Name;

注意看,Name 的类型为 TargetRelativeDirectPointer,它的定义如下:

template <typename Runtime, typename Pointee, bool Nullable = true>
using TargetRelativeDirectPointer = typename Runtime::template RelativeDirectPointer<Pointee, Nullable>;

TargetRelativeDirectPointer 只是一个别名的定义,那它具体的类型是 RelativeDirectPointer 这个模版类,我们来看一下 RelativeDirectPointer 的定义:

/// A direct relative reference to an object that is not a function pointer.
template <typename T, bool Nullable, typename Offset>
class RelativeDirectPointer<T, Nullable, Offset, 
                            typename std::enable_if<!std::is_function<T>::value>::type>
                            : private RelativeDirectPointerImpl<T, Nullable, Offset>
{
    using super = RelativeDirectPointerImpl<T, Nullable, Offset>;
    public:
    using super::get;
    using super::super;

    RelativeDirectPointer &operator=(T *absolute) & {
        super::operator=(absolute);
        return *this;
    }

    operator typename super::PointerTy() const & {
        return this->get();
    }

    const typename super::ValueTy *operator->() const & {
        return this->get();
    }

    using super::isNull;
};

这个类在干什么,注意看它的 PointerTy 方法和 *operator,我们可以理解为一个返回的是值,一个返回的是指针。并且 RelativeDirectPointer 继承自 RelativeDirectPointerImpl,在 RelativeDirectPointerImpl 中有一个成员变量,如下:

/// The relative offset of the function's entry point from *this.
Offset RelativeOffset;

分析到这里来之后,我们也可以把 TargetRelativeDirectPointer 还原出来了,代码如下:

struct TargetRelativeDirectPointer<Pointee> {
    var RelativeOffset: Int32

    mutating func getmeasureRelativeOffset() -> UnsafeMutablePointer<Pointee>{
        let offset = self.RelativeOffset

        return withUnsafePointer(to: &self) { p in
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self))
        }
    }
}

我们把 TargetEnumDescriptor 也给还原出来,代码如下:

struct TargetEnumDescriptor {
    var Flags: Int32
    var Parent: TargetRelativeDirectPointer<UnsafeRawPointer>
    var Name: TargetRelativeDirectPointer<CChar>
    var AccessFunctionPtr: TargetRelativeDirectPointer<UnsafeRawPointer>
    var Fields: TargetRelativeDirectPointer<UnsafeRawPointer>
    var NumPayloadCasesAndPayloadSizeOffset: UInt32
    var NumEmptyCases: UInt32
}

这个时候我们可以直接用 TargetRelativeDirectPointer 来充当我们的指针类型,这个 TargetRelativeDirectPointer 在 Swift 源码中叫做相对类型指针。这个东西在《方法》《属性》中也有提到相关的概念。

此时,我们也将 TargetEnumMetadata 的结构修改一下,代码如下:

struct TargetEnumMetadata {
    var Kind: Int
    var Description: UnsafeMutablePointer<TargetEnumDescriptor>
}

3 相对类型指针

什么叫相对类型指针呢,比如 TargetEnumDescriptor 中的 Name,这个 Name 存储的值并不是 Name 表意上的值,Name 存储的是一个叫做相对偏移量或者叫偏移信息。此时,我们拿到 Name 的值的内存地址做法是:Name 的内存地址 + 相对偏移量。在 Swift 里面有很多这样的偏移信息,这样做可以节省内存空间,避免存储大量的内存地址。

那我们通过一张图来进行理解: 相对偏移量流程图.png

在自定义的 TargetRelativeDirectPointer 类型中有一个泛型参数 Pointee,这个源自下面的代码:

/// The name of the type.
TargetRelativeDirectPointer<Runtime, const char, /*nullable*/ false> Name;

这个 Pointee 其实就是源码中定义的 const char,而这个 const char 就是 RelativeDirectPointerImpl 中传进来的 T,这个 T 很明显也是个泛型参数。这个就是自定义的 TargetRelativeDirectPointer 中泛型参数 Pointee 的由来。

所以,通过分析这个偏移信息的分析,我们才会模仿 C++ 指针偏移的实现,写了 getmeasureRelativeOffset 方法,用来获取成员值的真正的内存地址。

4 打印枚举中 Descriptor 的 Name

有了上面 3 点的铺垫之后,我们可以尝试获取枚举的名称并将其打印出来,代码如下:

enum Season {
    case spring
    case summer
    case autumn
    case winter
}

// Season.self 里存储的就是枚举的 metadata,通过 unsafeBitCast 函数强制转换成 TargetEnumMetadata 类型的指针
let season_matadata_ptr = unsafeBitCast(Season.self as Any.Type, to: UnsafeMutablePointer<TargetEnumMetadata>.self)
// 拿到 name 值的指针地址
let season_name_ptr = season_matadata_ptr.pointee.Description.pointee.Name.getmeasureRelativeOffset()
// 打印
print("name:",String(cString: season_name_ptr))
print("NumPayloadCasesAndPayloadSizeOffset:",season_matadata_ptr.pointee.Description.pointee.NumPayloadCasesAndPayloadSizeOffset)
print("NumEmptyCases:",season_matadata_ptr.pointee.Description.pointee.NumEmptyCases)
打印结果:
name: Season
NumPayloadCasesAndPayloadSizeOffset: 4
NumEmptyCases: 0

四、TargetClassMetadata

1. 还原 TargetClassMetadata、TargetClassDescriptor 和 FieldDescriptor 的结构

《结构体与类》这篇文章中我们曾经把 TargetClassMetadata 的结构还原出来过,并且在《方法》《属性》这两篇文章中也把 TargetClassDescriptor 的结构还原出来了。

根据之前的内容以及仿照第三点 TargetEnumMetadata 的原理分析,TargetClassMetadataTargetClassDescriptor 的结构如下:

struct TargetClassDescriptor{
    var flags: Int32
    var parent: Int32
    var name: TargetRelativeDirectPointer<CChar>
    var accessFunctionPointer: TargetRelativeDirectPointer<UnsafeRawPointer>
    var fields: 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
    }
}

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 Descriptor: UnsafeMutablePointer<TargetClassDescriptor>
    var iVarDestroyer: UnsafeRawPointer
}

接下来我们也将 TargetClassDescriptorfieldDescriptor 的结构还原出来:

struct FieldDescriptor {
    var MangledTypeName: TargetRelativeDirectPointer<CChar>
    var Superclass: TargetRelativeDirectPointer<CChar>
    var kind: UInt16
    var fieldRecordSize: Int16
    var numFields: Int32
    var fields: FiledRecordBuffer<FieldRecord>
}

struct FieldRecord {
    var fieldRecordFlags: Int32
    var mangledTypeName: TargetRelativeDirectPointer<CChar>
    var fieldName: TargetRelativeDirectPointer<UInt8>
}

struct FiledRecordBuffer<Element>{
    var element: Element

    mutating func buffer(n: Int) -> UnsafeBufferPointer<Element> {
         return withUnsafePointer(to: &self) {
             let ptr = $0.withMemoryRebound(to: Element.self, capacity: 1) { start in
                 return start
             }
             return UnsafeBufferPointer(start: ptr, count: n)
         }
     }

    mutating func index(of i: Int) -> UnsafeMutablePointer<Element> {
         return withUnsafePointer(to: &self) {
             return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
         }
     }
}

2. 打印类的信息

我们定义一个 SHPerson 类,并且初始化这个类,代码如下:

class SHPerson {
    var age = 18
    var name = "Coder_张三"
}

let p = SHPerson()

2.1 获取类的基本信息

接下来我们参照第三点中的第 4 小点,将类的一些基本信息进行打印,如下:

/SHPerson 的 metadata 转换成 TargetClassMetadata 指针
let person_matadata_ptr = unsafeBitCast(SHPerson.self as Any.Type, to: UnsafeMutablePointer<TargetClassMetadata>.self)

// --------- 获取类的基本信息 ---------
// 获取类名的内存地址
let person_name_ptr = person_matadata_ptr.pointee.Descriptor.pointee.name.getmeasureRelativeOffset()
print("类的名称:",String(cString: person_name_ptr))
// 获取当前属性的个数
let person_property_count = person_matadata_ptr.pointee.Descriptor.pointee.numFields
print("类的属性个数:",person_property_count)
// 获取父类
let person_superclass = person_matadata_ptr.pointee.superClass
print("父类:",person_superclass)
// 获取父类的 .Type
let person_superclass_type_ptr = person_matadata_ptr.pointee.Descriptor.pointee.superClassType.getmeasureRelativeOffset()
print("父类的类型:",String(cString: person_superclass_type_ptr))
打印结果:
类的名称: SHPerson
类的属性个数: 2
父类: _TtCs12_SwiftObject
父类的类型:

注意看,SHPeron 的父类是一个叫 _TtCs12_SwiftObject 的类,我们在《结构体与类》中分析得出类的本质是 HeapObject 的指针类型。那这个 _TtCs12_SwiftObject 可以理解为 Swift 类中隐藏的父类。我在源码中找到了这个类,其定义如下:

// Source code: "SwiftObject"
// Real class name: mangled "Swift._SwiftObject"
#define SwiftObject _TtCs12_SwiftObject

#if __has_attribute(objc_root_class)
__attribute__((__objc_root_class__))
#endif
SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
    @private
    Class isa;
    SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}

它是一个 OC 的类,包含两个成员,isarefCounts,那为什么要设计这个隐藏的父类呢。我在源码中查找的时候这个文件是在 runtime 的目录下,所以我猜测,这个类是用来兼容 OC 中的 runtime 机制的,以保证 Swift 的类在添加了某些关键字后支持 runtime 的特性。

2.2 获取类的属性信息

在第 1 点中,我们打印出了属性的个数,接下来我们将类的属性遍历出来,完整的代码如下:

// --------- 获取类的属性信息 ---------
let fieldOffsets = person_matadata_ptr.pointee.Descriptor.pointee.getFieldOffsets(UnsafeRawPointer(person_matadata_ptr).assumingMemoryBound(to: Int.self))
// 遍历
for i in 0..<person_property_count {
    print("------ begin - index: \(i) ------")

    // 1. 获取属性名称
    let fieldName_ptr = person_matadata_ptr.pointee.Descriptor.pointee.fieldDescriptor.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.fieldName.getmeasureRelativeOffset()
    print("属性名称:",String(cString:fieldName_ptr))

    // 2. 获取属性的类型信息
    let mangledTypeName = person_matadata_ptr.pointee.Descriptor.pointee.fields.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.mangledTypeName.getmeasureRelativeOffset()
    // 打印出来的是经过 Swift 混写过后的名称
    print("属性混写后的类型名称:",String(cString:mangledTypeName))

    // 3. 还原属性的类型名称
    let typeNameLength = Int32(256)
    let context = UnsafeRawPointer(person_matadata_ptr.pointee.Descriptor)

    // 当前的泛型参数:genericArgumentOffset 为泛型参数的偏移量,拿到泛型参数的偏移量之后移动当前的指针大小并将其绑定成 Any.Type 类型
    let genericVector = UnsafeRawPointer(person_matadata_ptr).advanced(by: person_matadata_ptr.pointee.Descriptor.pointee.genericArgumentOffset * MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: Any.Type.self)
    let genericArgs = UnsafeRawPointer(genericVector)?.assumingMemoryBound(to: Optional<UnsafeRawPointer>.self)

    // 获取属性的类型,这个函数是 swift 标准库里的 c 函数。
    // 第一个参数:属性的类型信息(地址)
    // 第二个参数:直接给 256 就行了
    // 第三个参数:告诉这个函数要在那里执行,而属性的信息是在当前类的描述信息,所以传的是一个 TargetClassDescriptor 的原生指针
    // 第四个参数:当前的泛型参数
    // 返回值:返回的是一个地址信息
    let filedType_ptr = _getTypeByMangledNameInContext(mangledTypeName, typeNameLength, genericContext: context, genericArguments: genericArgs)

    // 解析返回的 filedType 的地址信息
    // 直接强制转换拿到真实的属性的类型
    let filedType = unsafeBitCast(filedType_ptr, to: Any.Type.self)
    print("属性的类型:",filedType)

    // 获取属性值
    // 实例对象的内存地址
    let instanceAddress = Unmanaged.passUnretained(p as AnyObject).toOpaque()
    let value = customCast(type: filedType).get(from: instanceAddress.advanced(by: fieldOffsets[Int(i)]))
    print("属性的值:",value)

    print("------ end - index: \(i) ------")

}

代码中的第 1 和第 2 点都比较简单,只是层次深了一点,我们主要看第 3 点。首先,在第 2 点中我们已经拿到了属性的类型名称,但这个名称是经过 Swift 混写过后的名称,此时我们需要将其还原出来。

2.2.1 swift_getTypeByMangledNameInContext 函数

这个方法在 Swift 源码的** MetadataLookup.cpp** 文件中,我们来看一下它的实现:

SWIFT_CC(swift) SWIFT_RUNTIME_EXPORT
const Metadata * _Nullable
swift_getTypeByMangledNameInContext(
                             const char *typeNameStart,
                             size_t typeNameLength,
                             const TargetContextDescriptor<InProcess> *context,
                             const void * const *genericArgs) {
    llvm::StringRef typeName(typeNameStart, typeNameLength);
    SubstGenericParametersFromMetadata substitutions(context, genericArgs);
    return swift_getTypeByMangledName(MetadataState::Complete, typeName,
                                genericArgs,
                                [&substitutions](unsigned depth, unsigned index) {
                                    return substitutions.getMetadata(depth, index);
                                },
                                [&substitutions](const Metadata *type, unsigned index) {
                                    return substitutions.getWitnessTable(type, index);
                                }).getType().getMetadata();
}

注意,函数的返回值是 Metadata * 类型的,那这个类型不就是 Swift 中的 T.Type 么。它有四个参数,参数的含义如下:

  • typeNameStart:属性的类型信息(地址)。
  • typeNameLength:类型长度,一般直接给 256 就行了。
  • context:告诉这个函数要在那里执行,而属性的信息是在当前类的描述信息,所以传的是一个 TargetClassDescriptor 的原生指针。
  • genericArgs:当前的泛型参数。

这个函数是一个 C 函数,那我们怎么去调用它呢,在源码中是这么交互的:

@_silgen_name("swift_getTypeByMangledNameInContext")
public func _getTypeByMangledNameInContext(
    _ name: UnsafePointer<UInt8>,
    _ nameLength: UInt,
    genericContext: UnsafeRawPointer?,
    genericArguments: UnsafeRawPointer?)
    -> Any.Type?

我们也可以模仿源码,通过 @_silgen_name 将这个 C 函数映射成 Swift 函数,代码如下:

@_silgen_name("swift_getTypeByMangledNameInContext")
public func _getTypeByMangledNameInContext(
_ name: UnsafePointer<CChar>,
_ nameLength: UInt,
genericContext: UnsafeRawPointer?,
genericArguments: UnsafeRawPointer?)
-> Any.Type?

需要注意的是第四个参数,这个泛型参数在 metadataDescriptor 中,genericArgumentOffset 为泛型参数的偏移量,拿到泛型参数的偏移量之后移动当前的指针大小并将其绑定成 Any.Type 类型,再将其转成原生指针,就得到了我们需要的泛型参数。

最后,这个函数的返回值是一个地址信息,通过 unsafeBitCast 函数强制转换之后拿到的才是我们的属性类型。

2.2.2 获取属性的值

在获取值之前我们先做一些准备,先定义一个协议,这个协议里什么都不干:

protocol BrigeProtocol {}

此时我为这个协议添加一个 extension,代码如下:

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

这里面有一个方法,这方法的含义是,通过传一个实例的内存地址,通过 assumingMemoryBound(to:) 方法将这个参数绑定成 Self.self 类型的指针,最后通过这个指针取出指针的值。

这里需要注意!在这个方法里,Self 是什么,Self 就是这个协议的具体类型,比如,Int 类型的,此时 Self 就是 Int 类型的。String 类型的,此时 Self 就是 String 类型的。

接下来,我把协议的 Metadata 的结构给还原出来,还原出来的结构如下:

struct BrigeProtocolMetadata {
    // 元类型,真实的类型信息
    let type: Any.Type
    // //协议见证表
    let witness: Int
}

我们来看,第一个成员是一个元类型,这个元类型就是遵守这个协议的类型的元类型,第一个成员为协议见证表。接下来需要注意了,注意看这个方法:

func customCast(type: Any.Type) -> BrigeProtocol.Type {
    let container = BrigeProtocolMetadata(type: type, witness: 0)
    let protocolType = BrigeProtocol.Type.self
    let cast = unsafeBitCast(container, to: protocolType)
    return cast
}
  • 首先创建一个协议的 Metadata,注意看,传进来的 type 就是协议里的 typewitness 默认传个 0 就好了。

  • 其次,我们拿到 BrigeProtocol 的元类型,拿到元类型了之后,我们将自定义的协议的 Metadata 强制转换成 BrigeProtocol 的元类型,并且返回出去。

此时此刻,我们拿到这个返回的类型,就是 BrigeProtocol.Type 类型,那我们可以调用 extension 中的 get(from:) 方法,通过 assumingMemoryBound(to:) 方法将外部传进来的指针类型绑定成 Self.self 类型。

此时需要注意,在 customCast(type:) 方法中传的 type,必须是和 get(from:) 方法的参数是一一对应的。也就是 get(from:) 参数的真实类型必须是 customCast (type:) 方法的 type

那在这个过程中,我们就很巧妙的在协议中通过 Self 关键子,将这个内存地址还原出真实的指针类型,并且通过这个指针取出了这个内存地址的值。所以在最后,我们拿到实例的地址,取出这个实例中属性信息的偏移距离,通过 advanced 将属性值的内存地址给取出来,最后在通过 customCast(type:) get(from:) 方法将值取出。

那么,其打印结果如下:

------ begin - index: 0 ------
属性名称: age
属性混写后的类型名称: Si
属性的类型: Int
属性的值: 18
------ end - index: 0 ------
------ begin - index: 1 ------
属性名称: name
属性混写后的类型名称: SS
属性的类型: String
属性的值: Coder_张三
------ end - index: 1 ------

五、TargetStructDescriptor 源码探索

其实 TargetStructDescriptor 的结构比较简单,通过源码还原的话,你会发现它和 TargetEnumMetadata 的结构几乎是一摸一样的,还原的结构如下:

struct TargetStructMetadata {
    var Kind: Int
    var Description: UnsafeMutablePointer<TargetStructDescriptor>
}

struct TargetStructDescriptor{
    var Flags: Int32
    var Parent: Int32
    var Name: TargetRelativeDirectPointer<CChar>
    var AccessFunctionPtr: TargetRelativeDirectPointer<UnsafeRawPointer>
    var Fields: TargetRelativeDirectPointer<FieldDescriptor>
    var NumFields: UInt32
    var FieldOffsetVectorOffset: UInt32

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

    var genericArgumentOffset: Int {
        return 2
    }
}

接下来我们把结构体的属性信息像类的属性信息一样去打印,那其实这个过程和类基本差不多。这里主要是对 FieldDescriptor 做一些分析,然后模仿第四点将结构体的属性信息打印。

1. FieldDescriptor 分析

首先我们看 Fields,它是一个相对类型指针,这个指针包含的信息是一个 FieldDescriptor。它的结构我们曾经还原过,但在第四点中,我们在里面加了一个 fields,类型为 FiledRecordBuffer< FieldRecord >FiledRecordBuffer 里的两个方法不太好理解,我们来看 index(i:) 方法:

mutating func index(of i: Int) -> UnsafeMutablePointer<Element> {
    return withUnsafePointer(to: &self) {
        return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
    }
}

这个方法的作用是通过 i 来查找 FieldRecord,把当前 self 的指针类型绑定成 FieldRecord.Type 的类型,然后通过内存平移的方式来获取 FieldRecord。其实这个东西在源码中也可以找到,只是这里是通过 Swift 的语法实现的。

FieldDescriptor 的实现在源码 Records.h 文件中, getFields 函数的实现如下:

llvm::ArrayRef<FieldRecord> getFields() const {
    return {getFieldRecordBuffer(), NumFields};
}

注意看,这个方法就是获取 fields 的方法,fields 存的是 FieldRecords,怎么知道的呢,我们来看 getFieldRecordBuffer 函数:

const FieldRecord *getFieldRecordBuffer() const {
    return reinterpret_cast<const FieldRecord *>(this + 1);
}

这个方法通过 reinterpret_cast(this + 1) 强制转换成 FieldRecord * 类型,注意!它返回的是指针类型,那么结合这两个方法,我们是不是可以知道,这个 fields 是一块连续的内存空间,这一块连续的内存空间存储的是 FieldRecord 类型,并且 NumFields 是它的容量大小。

其实这一块比较难理解的就是 (this + 1) ,在 C++ 中, this 是一个指向该对象的指针,由于它是一个指针,因此它可以应用指针算术甚至数组索引。如果这个 this 是数组中的一个元素,(this + 1) 则将指向数组中的下一个对象。如果不是,那么它只会将该内存中的任何内容与该对象相同,除非它是相同的类型,否则这将是未定义的行为。

我们来看一下,源码中的 TargetStructDescriptor 是如何定义 Fields 的:

/// A pointer to the field descriptor for the type, if any.
TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor, /*nullable*/ true> Fields;

注释上说它是指向该类型的字段描述符的指针,那么也就意味 Fields 是一块连续的存储空间,可以把它想象成数组(注意,它不是真正的数组)。那么这个内存空间存储的就是一个 FieldDescriptor 和若干个 FieldRecord。所以,在获取 FieldRecord 的时候可以在 FieldDescriptor 中通过 (this + 1) 的方法拿到 FieldRecord

这个就是我们在 FieldDescriptor 中添加了自定义的 fileds 的由来,通过它可以获取到想要的 FieldRecord,从而得到属性信息。

2. 打印结构体的信息

struct SHPerson {
    var age = 18
    var name = "Coder_张三"
}

var p = SHPerson()

// 将 SHPerson 的 metadata 转换成 TargetStructMetadata 指针
let person_matadata_ptr = unsafeBitCast(SHPerson.self as Any.Type, to: UnsafeMutablePointer<TargetStructMetadata>.self)

// --------- 获取结构体的基本信息 ---------
// 获取结构体名的内存地址
let person_name_ptr = person_matadata_ptr.pointee.Description.pointee.Name.getmeasureRelativeOffset()
print("结构体的名称:",String(cString: person_name_ptr))
// 获取当前属性的个数
let person_property_count = person_matadata_ptr.pointee.Description.pointee.NumFields
print("结构体的属性个数:",person_property_count)

// --------- 获取结构体的属性信息 ---------
print("")

let fieldOffsets = person_matadata_ptr.pointee.Description.pointee.getFieldOffsets(UnsafeRawPointer(person_matadata_ptr).assumingMemoryBound(to: Int.self))

// 遍历
for i in 0..<person_property_count {
    print("------ begin - index: \(i) ------")

    // 1. 获取属性名称
    let fieldName_ptr = person_matadata_ptr.pointee.Description.pointee.Fields.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.fieldName.getmeasureRelativeOffset()
    print("属性名称:",String(cString:fieldName_ptr))

    // 2. 获取属性的类型信息
    let mangledTypeName = person_matadata_ptr.pointee.Description.pointee.Fields.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.mangledTypeName.getmeasureRelativeOffset()
    // 打印出来的是经过 Swift 混血过后的名称
    print("属性混写后的类型名称:",String(cString:mangledTypeName))

    // 3. 还原属性的类型名称
    let typeNameLength = UInt(256)
    let context = UnsafeRawPointer(person_matadata_ptr.pointee.Description)

    // 当前的泛型参数:genericArgumentOffset 为泛型参数的偏移量,拿到泛型参数的偏移量之后移动当前的指针大小并将其绑定成 Any.Type 类型
    let genericVector = UnsafeRawPointer(person_matadata_ptr).advanced(by: person_matadata_ptr.pointee.Description.pointee.genericArgumentOffset * MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: Any.Type.self)
    let genericArgs = UnsafeRawPointer(genericVector)?.assumingMemoryBound(to: Optional<UnsafeRawPointer>.self)

    // 获取属性的类型,这个函数是 swift 标准库里的 c 函数。
    // 第一个参数:属性的类型信息(地址)
    // 第二个参数:直接给 256 就行了
    // 第三个参数:告诉这个函数要在那里执行,而属性的信息是在当前类的描述信息,所以传的是一个 TargetClassDescriptor 的原生指针
    // 第四个参数:当前的泛型参数
    // 返回值:返回的是一个地址信息
    let filedType_ptr = _getTypeByMangledNameInContext(mangledTypeName, typeNameLength, genericContext: context, genericArguments: genericArgs)

    // 解析返回的 filedType 的地址信息
    // 直接强制转换拿到真实的属性的类型
    let filedType = unsafeBitCast(filedType_ptr, to: Any.Type.self)
    print("属性的类型:",filedType)

    // 获取属性值
    // 实例的内存地址
    let instanceAddress = withUnsafePointer(to: &p) { UnsafeRawPointer($0) }
    let value = customCast(type: filedType).get(from: instanceAddress.advanced(by: fieldOffsets[Int(i)]))
    print("属性的值:",value)

    print("------ end - index: \(i) ------")
}
打印结果:
结构体的名称: SHPerson
结构体的属性个数: 2

------ begin - index: 0 ------
属性名称: age
属性混写后的类型名称: Si
属性的类型: Int
属性的值: 18
------ end - index: 0 ------
------ begin - index: 1 ------
属性名称: name
属性混写后的类型名称: SS
属性的类型: String
属性的值: Coder_张三
------ end - index: 1 ------

六、HandyJson

平时的开发中我们请求网络数据返回的大多都是 json 格式的数据,那不可避免的就是需要对网络数据进行处理。在 OC 中,为了快速开发,我们通常会使用各种第三方 json 转 model 的框架,比如 YYModel。在 OC 中这些框架的实现是少不了 runtime 的机制。但纯粹的 Swift,是不具备 runtime 的,但 Swift 也有类似 json 转 model 的框架,其中的一个就是 HandyJson。

HandyJSON 的基本原理就是从类信息里获取所有属性的特征,包括名称,属性在内存里的偏移量、属性的个数、属性的类型等等,然后将服务端返回来的数据用操作内存的方式将数值写入对应的内存,来实现 json 转 model。它的使用方式方法可以去看看官方文档。

在第四点里我们通过反射机制从类的 Metadata 中获取来所有属性的相关信息,其中最难的一点是还原属性的类型和获取属性的值。在还原的时候我们还用到了 Swift 标准库的一个 C 函数 - swift_getTypeByMangledNameInContext,那其实在 HandyJson 中也是用这个,并且 HandyJson 也是用 @_silgen_name 将这个函数映射成 _getTypeByMangledNameInContext Swift 方法,那我们来看一下 HandyJson 是怎么使用的:

HandyJson还原属性类型的方法.png

那在结合第四点的描述,属性的类型这个时候就被还原出来了,那么接下来就是通过一个协议进行扩展,用协议的 Metadata,才将这个属性的值给获取到。

其实这个思路也是参照了 HandyJson 的使用,我们来看一下:

将 Any.Type 绑定成 AnyExtensions.Type 类型.png

注意看,HandyJson 也是用了一个协议 - AnyExtensions,我们看 extensions(type:) 方法,这个方法的实现效果不就是和我们实现的 customCast(type:) 是一样的吗,只是我们的比较暴力,直接通过 unsafeBitCast 函数强制转换了。我们再来看 HandyJson 的一个方法:

public static func write(_ value: Any, to storage: UnsafeMutableRawPointer) {
    guard let this = value as? Self else {
        return
    }
    storage.assumingMemoryBound(to: self).pointee = this
}

注意看这个 this!熟不熟悉,HandyJson 很巧妙的用了 Self 关键字,把 value 的真实类型给还原了出来,那这一步也是和我们写的 get(from:) 方法是相似的,只不过人家处理的更加好!

其实我觉得写这个 HandyJson 的人一定是对 Swift 类、结构体、枚举等内部的 Metadata 的结构特别的属性,他们在文档的也提到了这个库借鉴的库,它这种通过反射机制来获取属性的信息,然后通过对内存的直接操作来实现 json 转 model 的方式比 OC 通过 runtime 去实现要快得多,性能相对 runtime 也肯定是比较高的。需要注意的是它强烈依赖 Metadata 结构,将来苹果要是把这个结构一改就不能用了。