Meta-types

1. Any and AnyObject

Swift provides two special types: Any and AnyObject. Any: Can represent Any type (enumerations, structs, classes, and function types). AnyObject: Can represent any class type.

Write AnyObject after the protocol to indicate that only classes are subject to this protocol. Write class after the protocol to indicate that only classes are subject to this protocol. As shown in figure:

2. Is and the as

Next I declare some classes and protocols as follows:

protocol SHRunnable {
    func run(a)
}

class SHPerson {}class SHStudent: SHPerson.SHRunnable {
    func run(a) {
        print("run")}func study(a) {
        print("study")}}Copy the code

Is is used to determine whether it is of a type:

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

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

stu = "Coder_ zhang SAN"
print(stu is String)    // true
Copy the code

As is used for casting:

var stu: Any = 10
// Will be converted to SHStudent? type
(stu as? SHStudent)?.run()

stu = SHStudent()
(stu as? SHStudent)?.study()   // study
Copy the code

When casting with AS, we may need to put a question mark (?) after as. . In the code above, the compiler converts it to an optional SHStudent, so the call to run is preceded by a question mark (?). .

In addition to the question mark (?) We can also use exclamation marks (!) . The code is as follows:

(stu as! SHStudent).study()
Copy the code

Use an exclamation mark (!) This represents a forced unpacking of the optional SHStudent, resulting in a cast of type SHStudent, so there is no need to call the method with a question mark (?). .

When do you not use a question mark? And or exclamation point (!) The cast is 100 percent successful. Here’s an example:

var d = 10 as Double
/ / or
var data = [Any]()
data.append(Int("123") as Any)
Copy the code

3. T. Elf, T. type, AnyClass

T. elf: T is the instance object. Currently, t. elf returns the instance object itself. If T is a class, the current t. elf returns the metatype.

What does that mean? Here’s the code:

class SHPerson {
    var age: Int = 18
    var name: String = "Coder_ zhang SAN"
}

let p = SHPerson(a)let p_self = p.self
print("end")
Copy the code

Print the breakpoint at print and format the memory for p and p_self, as shown here:

Next we replace p.elf with shperson. self and format the output, as shown in the figure below:

So, in T. elf, when T is the instance object, T. elf returns the instance object itself. When T is a class, t. elf returns a metatype, which is called metadata in the structure and Classes article.

T.elf belongs to the t.type type.

var p: SHPerson = SHPerson(a)var p_type: SHPerson.Type = SHPerson.self
Copy the code

We can also interpret shperson.type this way. First, the variable p is of type SHPerson. P_type is of Type shPerson.type.

Next, we’ll introduce something called AnyClass. Let’s look at its definition:

public typealias AnyClass = AnyObject.Type
Copy the code

This is an alias for anyObject. Type. AnyObject stands for any class Type.

let objcType: AnyObject.Type = SHPerson.self
Copy the code

AnyClass is an alias for anyObject. Type. We can use AnyClass instead of anyObject. Type.

let objcType: AnyClass = SHPerson.self
Copy the code

4. Self

4.1 type(of:)

Type (of:) returns the dynamic type of a value. Take a look at the following code:

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

let count: Int = 5
printInfo(count)
Copy the code

The argument to printInfo(:) is of type Any, and I pass an Int when I call printInfo(:). In this case, inside the function, the value is of type Any, so we can use type(of:) to get the specific type of value.

In printInfo(:), value is of type Any at the syntax level, which is called static typing. And the value THAT I’m passing is of type Int, so this is the dynamic type of value. For details about type(of:), see apple’s official documentation.

4.2 The usage of Self

Next, Self, which stands for the current type, is generally used as the type of the return value, with the qualification that the return value and the method caller must be of the same type (and can also be used as the parameter type). The code is as follows:

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

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

let p = SHPerson(a)print(p.mySelf())       // Project name.shperson
p.printSelf(p.mySelf()) // Project name.shperson
Copy the code

Second, the Mirror

1. The first Mirror

The name translates directly to mirror, which is a representation of the substructure and display style of any type of instance. Mirrors describe the parts that make up a particular instance, such as the instance’s storage properties, collection or tuple elements, or its active enumeration cases. The mirror also provides a display style property that suggests how to render the mirror.

Swift’s reflection mechanism is based on structure Mirror. The so-called reflection mechanism can dynamically obtain type and member information, and can call methods and attributes at run time.

There is little emphasis on reflection when developing with OC because the OC Runtime is much more powerful than reflection in other languages. But Swift is a type-safe language that does not allow us to operate directly like OC, and its standard library still provides a reflection mechanism for accessing member information.

The Mirror initialization method is as follows:

// create a mirror that reflects the given instance.
/// subject: create mirrored instance for it.
public init(reflecting subject: Any)
Copy the code

Mirror has a children property that we can use to get information about instances of reflection. Its definition is as follows:

public let children: Mirror.Children
Copy the code

It is of type mirror. Children, which is an alias of AnyCollection< mirror. Child>. Mirror.Child is a tuple. Let’s look at their definitions:

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

public typealias Children = AnyCollection<Mirror.Child>
Copy the code

It has more properties, let’s look at the following three:

// The type of the reflected object
public let subjectType: Any.Type
// Type of reflection
public let displayStyle: Mirror.DisplayStyle?
// Mirror of the object's parent class
public var superclassMirror: Mirror? { get }

// Other properties and methods
/ /...
Copy the code

Create a Mirror instance to iterate through the class attributes, as follows:

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

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

// Pass a Mirror of Any type.
let p_mirror = Mirror(reflecting: p)
for child in p_mirror.children {
print(child.label ?? "".":",child.value)
}
Copy the code
Print result: age:18
name : Coder_ zhang SAN
weight : 60.0
height : 180.0
Copy the code

The property name of the object and the value of the property were successfully printed.

2. Mirror source code exploration

Swift: Mirror () : Mirror () : Mirror () : Mirror () : Mirror () : Mirror () : swift () : Mirror () : Mirror ()

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

This initialization function determines whether the subject complies with the CustomReflectable protocol, complies with the Instance of the CustomReflectable protocol, and calls the customMirror property directly. As you can probably guess from the name, the Mirror returned by the instance that complies with the CustomReflectable protocol needs to be customized. If you do not comply with the CustomReflectable protocol, perform lower-level function calls.

Here I print out the contents of instance P with Po P. What I print out is the address of instance P.

But I want to print the structure of instance P by following the CustomReflectable protocol:

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
    }
}
Copy the code

After re-po p, the property information of SHPerson is printed.

Swift: reflectionMirror.swift: reflectionMirror.swift: reflectionMirror.swift: reflectionMirror.swift: reflectionMirror.swift: reflectionMirror.swift: reflectionMirror.swift: reflectionMirror.swift: reflectionMirror.swift: reflectionmirror.swift: reflectionmirror.swift

internal init(internalReflecting subject: Any.subjectType: Any.Type? = nil.customAncestor: Mirror? = nil)
{
    // 1. Check whether the instance type is nil. If it is nil, get the instance type -t.type
    let subjectType = subjectType ?? _getNormalizedType(subject, type: type(of: subject))

    // 2. Obtain attribute information from 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. Obtain the parent 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. Set 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. Set subjectType and _defaultDescendantRepresentation
    self.subjectType = subjectType
    self._defaultDescendantRepresentation = .generated
}
Copy the code

We see the first line of code that gets the type of the subject by calling the _getNormalizedType function. Let’s look at the _getNormalizedType function:

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

Notice the compiled field @_silgen_name used above the function, which is a hidden symbol for Swift that maps a C/C++ function directly to Swift. When we call _getNormalizedType, we are essentially calling 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; });
}
Copy the code

The implementation of this function is in the reflectionMirror. CPP file, which calls a call function that has a callback function that can be understood as a Swift closure. Different types can be returned via the call function. Let’s look at other calls to the call function, as follows:

In fact, there are many places to call the call function, here is only two screenshots, interested can go to the source code to see, let’s take a look at the call function implementation, call function implementation is quite long, I first post part:

What do we mean by class calls and non-class calls in the diagram, such as non-class calls that assign type and value to the corresponding impL member in the callback function? The impL type is a ReflectionMirrorImpl type, which is a template class.

Let’s move on to the remaining implementation of the call function, as follows:

It determines the type of the instance by kind, gets the corresponding impL for each type, and calls a non-class call(&impl). The class is calling callClass.

Let’s choose an impL, such as enumer-enumimpl, as shown in the figure below:

The isReflectable () function is used to determine whether reflection is accepted or not. The implementation of this function is to get the Description function. The isReflectable function is used to retrieve count as follows:

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;
}
Copy the code

If reflection is not supported, return 0, otherwise go below and call a getInfo function. What about the getInfo function, which is implemented as follows:

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();
}
Copy the code

I went straight to the key code:

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

The getFieldAt implementation reads in part:

Here we get Description, and then we get the Fields, and the Fields contain the attribute information, and now that we get the Fields, we can get the attribute information.

TargetEnumMetadata source code exploration

1. Restore the TargetEnumMetadata structure

TargetEnumMetadata is derived from TargetValueMetadata. In TargetValueMetadata, there is a member variable Description:

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

TargetValueMetadata inherits from TargetMetadata. In TargetMetadata, there is a member variable Kind:

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

At this point, the TargetEnumMetadata structure can be restored, the code is as follows:

struct TargetEnumMetadata {
    var Kind: Int
    var Description: UnsafeRawPointer
}
Copy the code

2 reduction TargetEnumDescriptor and TargetRelativeDirectPointer structure

At this point, we also need to restore the Description of the structure, although the Description in the definition is TargetValueTypeDescriptor type, But in TargetEnumMetadata it should be of type TargetEnumDescriptor. In the TargetEnumMetadata source structure, the method of retrieving the Description is as follows:

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

So, if we want to restore the Description of target Metadata we need to start with target Descriptor. In structs and Classes, when we restore the Description of a class, the type of the member variable is usually Int32 or UnsafeRawPointer. Then we will directly restore the type of its members.

We find TargetTypeContextDescriptor, this class is TargetEnumDescriptor parent the parent class, I take one of the member variables, as follows:

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

Look at, the type of the Name as TargetRelativeDirectPointer, its definition is as follows:

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

TargetRelativeDirectPointer is just an alias definition, it is specific type RelativeDirectPointer this template class, we look at the definition of 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(a); } const typenamesuper: :ValueTy *operator->() const & {
        return this->get(a); } usingsuper::isNull;
};
Copy the code

What does this class do? Look at its PointerTy methods and *operator. We can understand that one returns a value and one returns a pointer. And RelativeDirectPointer inherited from RelativeDirectPointerImpl, one of the members in the RelativeDirectPointerImpl variables, as follows:

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

Analysis here, we can also restore a TargetRelativeDirectPointer came out, the code is as follows:

struct TargetRelativeDirectPointer<Pointee> {
    var RelativeOffset: Int32

    mutating func getmeasureRelativeOffset(a) -> 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))}}}Copy the code

Let’s restore the TargetEnumDescriptor as follows:

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
}
Copy the code

This time we can directly use TargetRelativeDirectPointer to act as our pointer type, in a Swift source this TargetRelativeDirectPointer called relative type pointer. This thing is also mentioned in methods and Attributes.

At this point, we also modify the structure of TargetEnumMetadata as follows:

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

3 Pointers to relative types

What is a pointer to a relative type, for example, Name in TargetEnumDescriptor, the value that the Name stores is not the value of the Name notation, the Name stores something called a relative offset or an offset information. At this point, we get the memory address of the value of Name: memory address of Name + relative offset. Swift has a lot of this offset information, so it saves memory space and avoids storing a lot of memory addresses.

Let’s take a picture to understand:

In custom TargetRelativeDirectPointer types have a generic parameter Pointee, this stems from the following code:

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

The Pointee is source defined in the const char, and the const char is RelativeDirectPointerImpl incoming T, the T obviously is a generic type parameters. This is a custom TargetRelativeDirectPointer Pointee generic parameters in origin.

So, by analyzing the offset information, we will imitate the C++ pointer offset implementation, write getmeasureRelativeOffset method, used to obtain the real memory address of the member value.

4 Print the Name of an enumeration Descriptor

With the above three points in mind, we can try to get the enumeration name and print it out as follows:

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

// Season.self stores the enumerated metadata, which is cast to a pointer of type TargetEnumMetadata by the unsafeBitCast function
let season_matadata_ptr = unsafeBitCast(Season.self as Any.Type, to: UnsafeMutablePointer<TargetEnumMetadata>.self)
// Get the pointer address of name
let season_name_ptr = season_matadata_ptr.pointee.Description.pointee.Name.getmeasureRelativeOffset()
/ / print
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)
Copy the code
Print result: name:Season
NumPayloadCasesAndPayloadSizeOffset: 4
NumEmptyCases: 0
Copy the code

Four, TargetClassMetadata

1. Restore the structure of TargetClassMetadata, TargetClassDescriptor, and FieldDescriptor

The structure of TargetClassMetadata was restored in structures and Classes, and the structure of TargetClassMetadata was restored in methods and Properties.

The structure of TargetClassMetadata and TargetClassDescriptor is as follows:

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
}

Copy the code

Next we restore the structure of the fieldDescriptor in the TargetClassDescriptor as well:

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))
         }
     }
}
Copy the code

2. Print class information

We define a SHPerson class and initialize it as follows:

class SHPerson {
    var age = 18
    var name = "Coder_ zhang SAN"
}

let p = SHPerson(a)Copy the code

2.1 Obtaining Basic Class Information

Next, we refer to the fourth point in the third point to print some basic information about the class, as follows:

/ å°† SHPersonMetadata is converted toTargetClassMetadataPointer to thelet person_matadata_ptr = unsafeBitCast(SHPerson.self as Any.Type, to: UnsafeMutablePointer<TargetClassMetadata>.self)

// --------- Obtain basic information about the class ---------
// Get the memory address of the class name
let person_name_ptr = person_matadata_ptr.pointee.Descriptor.pointee.name.getmeasureRelativeOffset()
print("Class name:".String(cString: person_name_ptr))
// Get the number of current attributes
let person_property_count = person_matadata_ptr.pointee.Descriptor.pointee.numFields
print("Number of attributes of class:",person_property_count)
// Get the parent class
let person_superclass = person_matadata_ptr.pointee.superClass
print("Parent class:",person_superclass)
// Get the.type of the parent class
let person_superclass_type_ptr = person_matadata_ptr.pointee.Descriptor.pointee.superClassType.getmeasureRelativeOffset()
print("Type of parent class:".String(cString: person_superclass_type_ptr))
Copy the code
Print result: Class name:SHPersonNumber of class attributes:2Parent class: _TtCs12_SwiftObject Type of parent class:Copy the code

Note that SHPeron’s parent class is a class called _TtCs12_SwiftObject, which we analyzed in “Structures and Classes” is essentially a pointer type of HeapObject. So this _TtCs12_SwiftObject can be thought of as the hidden parent of the Swift class. I found this class in the source code, with the following definition:

// 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;
}
Copy the code

It is an OC class with two members, ISA and refCounts, so why design this hidden parent class? When I look in the source code, this file is in the Runtime directory, so I guess this class is designed to be compatible with the RUNTIME mechanism in OC, to ensure that Swift classes support Runtime features after adding certain keywords.

2.2 Obtaining Class Attribute Information

In point 1, we print out the number of attributes, then we iterate through the class attributes, the complete code is as follows:

// --------- get the attribute information of the class ---------
let fieldOffsets = person_matadata_ptr.pointee.Descriptor.pointee.getFieldOffsets(UnsafeRawPointer(person_matadata_ptr).assumingMemoryBound(to: Int.self))
/ / traverse
for i in 0..<person_property_count {
    print("------ begin - index: \(i)-- -- -- -- -- -")

    // 1. Get the attribute name
    let fieldName_ptr = person_matadata_ptr.pointee.Descriptor.pointee.fieldDescriptor.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.fieldName.getmeasureRelativeOffset()
    print("Attribute Name:".String(cString:fieldName_ptr))

    // 2. Get the type information of the attribute
    let mangledTypeName = person_matadata_ptr.pointee.Descriptor.pointee.fields.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.mangledTypeName.getmeasureRelativeOffset()
    // Print the names that have been mixed by Swift
    print("Attribute mixed type name:".String(cString:mangledTypeName))

    // 3. Restore the type name of the attribute
    let typeNameLength = Int32(256)
    let context = UnsafeRawPointer(person_matadata_ptr.pointee.Descriptor)

    // Current Generic arguments: genericArgumentOffset is the offset of the generic argument. After taking the offset of the generic argument, move the current pointer size and bind it to 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)

    // Get the type of the attribute. This function is a c function in the Swift standard library.
    // The first argument is the property's type information (address).
    // The second argument: just give 256
    // Third argument: tells the function where to execute, and the property is the description of the current class, so it passes a native pointer to the TargetClassDescriptor
    // The fourth parameter: the current generic parameter
    // Return value: an address is returned
    let filedType_ptr = _getTypeByMangledNameInContext(mangledTypeName, typeNameLength, genericContext: context, genericArguments: genericArgs)

    // Parse the address information for the returned filedType
    // Directly cast to get the real property type
    let filedType = unsafeBitCast(filedType_ptr, to: Any.Type.self)
    print("Attribute type:",filedType)

    // Get the attribute value
    // Memory address of the instance object
    let instanceAddress = Unmanaged.passUnretained(p as AnyObject).toOpaque()
    let value = customCast(type: filedType).get(from: instanceAddress.advanced(by: fieldOffsets[Int(i)]))
    print("Attribute value:",value)

    print("------ end - index: \(i)-- -- -- -- -- -")}Copy the code

Points 1 and 2 in the code are relatively simple, but a bit deeper. Let’s focus on point 3. First, in point 2 we’ve got the type name of the attribute, but it’s the name that Swift has scrambled, and we need to restore it.

2.2.1 swift_getTypeByMangledNameInContext function

This method is in the Swift source ** metadatalookup. CPP ** file, let’s look at its implementation:

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) {
                                    returnsubstitutions.getMetadata(depth, index); },&substitutions](const Metadata *type, unsigned index) {
                                    return substitutions.getWitnessTable(type, index);
                                }).getType().getMetadata();
}
Copy the code

The return value of this function is Metadata *. This type is t.type in Swift. It takes four parameters, which have the following meanings:

  • TypeNameStart: Type information (address) for the property.
  • TypeNameLength: Type length, usually 256.
  • Context: tells this function where to execute, and the property information is the description of the current class, so it passes a native pointer to a TargetClassDescriptor.
  • GenericArgs: Current generic parameter.

This function is a C function, so how to call it, in the source code is like this interaction:

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

We can also copy the source code and map the C function to Swift with @_silgen_name:

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

GenericArgumentOffset is the offset of the generic parameter. After taking the offset of the generic parameter, move the current pointer size and bind it to Any.Type. Turning it into a native pointer gives us the generic parameters we need.

Finally, the return value of this function is an address, which is our property type after the unsafeBitCast.

2.2.2 Obtaining the value of an attribute

Before we get the value, let’s do a few things: define a protocol that does nothing:

protocol BrigeProtocol {}
Copy the code

At this point I’ll add an extension to the protocol with the following code:

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

The sumingmemorybound (to:) method is used to fetch the value of a pointer to self.self by passing an instance’s memory address into the sumingmemorybound (to:) method.

Attention here! What is Self in this method, Self is the concrete type of this protocol, for example, of Int, in which case Self is Int. String, in which case Self is String.

Next, I put the protocol Metadata structure to restore, restore the structure is as follows:

struct BrigeProtocolMetadata {
    // Metatype, real type information
    let type: Any.Type
    // // Agreement Witness Table
    let witness: Int
}
Copy the code

Let’s see, the first member is a mettype, which is the mettype of the type that complies with the protocol, and the first member is the protocol witness table. Now notice, notice this method:

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
}
Copy the code
  • Create the Metadata for the protocol. Note that the type in the Metadata is the type in the protocol.

  • Second, we get the BrigeProtocol metatype. Once we get the metatype, we cast the custom protocol Metadata to the BrigeProtocol metatype and return it.

At this point, we have the return Type, BrigeProtocol.Type, so we can call the get(from:) method in extension, The sumingmemorybound (to:) method binds externally passed pointer types to self.self.

Note that the type passed in the customCast(type:) method must correspond to the parameters of the get(from:) method. That is, the real type of the get(from:) argument must be the type of the customCast (type:) method.

So, in the process, we cleverly revert the memory address back to the real pointer type in the protocol using the Self key, and extract the value of the memory address through the pointer. So finally, we get the address of the instance, get the offset distance of the attribute information in the instance, use advanced to get the memory address of the attribute value, and finally use customCast(type:) and get(from:) to get the value.

Then, the print result is as follows:

------ begin - index: 0 ------Attribute name: age The type name of the mixed attribute:SiAttribute type:IntProperty value:18
------ end - index: 0 ------
------ begin - index: 1 ------Attribute name: name Mixed type name of the attribute:SSAttribute type:StringProperty value:Coder_ zhang SAN
------ end - index: 1 ------
Copy the code

TargetStructDescriptor source code exploration

The structure of TargetStructDescriptor is relatively simple. If you restore it from source code, you will find that TargetEnumMetadata is almost identical to TargetEnumMetadata. The structure of TargetStructDescriptor is as follows:

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}}Copy the code

The next step is to print the structure’s property information as if it were a class’s property information. So basically, you do a little bit of analysis of the FieldDescriptor, and then you copy the fourth point and print the property information of the structure.

1. FieldDescriptor analysis

So first we look at Fields, which is a pointer to a relative type, and the information that this pointer contains is a FieldDescriptor. The structure of the FiledRecordBuffer was restored before, but in point 4 we added a field of type FiledRecordBuffer< FieldRecord >. The two methods in FiledRecordBuffer are not easy to understand. Let’s look at 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))
    }
}
Copy the code

This method looks for a FieldRecord by I, binds the current self pointer Type to fieldRecord. Type, and retrieves the FieldRecord by memory translation. In fact, this thing can be found in the source code, but here is through the Swift syntax implementation.

In the records.h file, the getFields function is implemented as follows:

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

GetFieldRecordBuffer getFieldRecordBuffer getFieldRecordBuffer getFieldRecordBuffer getFieldRecordBuffer

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

This method casts (this + 1) to FieldRecord * via reinterpret_cast. Note! This field is a contiguous memory space that holds FieldRecord, and NumFields is its size.

In C++, this is a pointer to that object, and since it’s a pointer, it can apply pointer arithmetic or even array indexing. If this is an element in the array, (this + 1) points to the next object in the array. If not, then it will only treat anything in that memory as the same as the object, which is undefined behavior unless it is of the same type.

Let’s look at how Fields are defined in the source TargetStructDescriptor:

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

The comment says that it is a pointer to the field descriptor of that type, which means that Fields is a contiguous block of storage and can be thought of as an array (note that it is not really an array). So this memory space is going to store one FieldDescriptor and several field records. So, when you get a FieldRecord, you can get a FieldRecord in a FieldDescriptor by (this + 1).

So that’s why we added custom fileds to the FieldDescriptor, to get the desired FieldRecord, to get the property information.

2. Print structure information

struct SHPerson {
    var age = 18
    var name = "Coder_ zhang SAN"
}

var p = SHPerson(a)// Convert SHPerson's metadata to a TargetStructMetadata pointer
let person_matadata_ptr = unsafeBitCast(SHPerson.self as Any.Type, to: UnsafeMutablePointer<TargetStructMetadata>.self)

// --------- Obtain basic information about the structure ---------
// Get the memory address of the structure name
let person_name_ptr = person_matadata_ptr.pointee.Description.pointee.Name.getmeasureRelativeOffset()
print("Structure name:".String(cString: person_name_ptr))
// Get the number of current attributes
let person_property_count = person_matadata_ptr.pointee.Description.pointee.NumFields
print("Number of attributes of structure:",person_property_count)

// --------- get the attribute information of the structure ---------
print("")

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

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

    // 1. Get the attribute name
    let fieldName_ptr = person_matadata_ptr.pointee.Description.pointee.Fields.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.fieldName.getmeasureRelativeOffset()
    print("Attribute Name:".String(cString:fieldName_ptr))

    // 2. Get the type information of the attribute
    let mangledTypeName = person_matadata_ptr.pointee.Description.pointee.Fields.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.mangledTypeName.getmeasureRelativeOffset()
    // Print the name of the Swift hybrid
    print("Attribute mixed type name:".String(cString:mangledTypeName))

    // 3. Restore the type name of the attribute
    let typeNameLength = UInt(256)
    let context = UnsafeRawPointer(person_matadata_ptr.pointee.Description)

    // Current Generic arguments: genericArgumentOffset is the offset of the generic argument. After taking the offset of the generic argument, move the current pointer size and bind it to 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)

    // Get the type of the attribute. This function is a c function in the Swift standard library.
    // The first argument is the property's type information (address).
    // The second argument: just give 256
    // Third argument: tells the function where to execute, and the property is the description of the current class, so it passes a native pointer to the TargetClassDescriptor
    // The fourth parameter: the current generic parameter
    // Return value: an address is returned
    let filedType_ptr = _getTypeByMangledNameInContext(mangledTypeName, typeNameLength, genericContext: context, genericArguments: genericArgs)

    // Parse the address information for the returned filedType
    // Directly cast to get the real property type
    let filedType = unsafeBitCast(filedType_ptr, to: Any.Type.self)
    print("Attribute type:",filedType)

    // Get the attribute value
    // Memory address of the instance
    let instanceAddress = withUnsafePointer(to: &p) { UnsafeRawPointer($0)}let value = customCast(type: filedType).get(from: instanceAddress.advanced(by: fieldOffsets[Int(i)]))
    print("Attribute value:",value)

    print("------ end - index: \(i)-- -- -- -- -- -")}Copy the code
Structure name:SHPersonNumber of struct attributes:2

------ begin - index: 0 ------Attribute name: age The type name of the mixed attribute:SiAttribute type:IntProperty value:18
------ end - index: 0 ------
------ begin - index: 1 ------Attribute name: name Mixed type name of the attribute:SSAttribute type:StringProperty value:Coder_ zhang SAN
------ end - index: 1 ------
Copy the code

Six, HandyJson

In the usual development, most of the data we request from the network are returned in JSON format, so it is inevitable to process the network data. In OC, we often use various third-party JSON-to-Model frameworks, such as YYModel, for rapid development. These frameworks are implemented in OC without the runtime mechanism. Swift doesn’t have a Runtime, but Swift does have a jSON-to-Model framework, one of which is HandyJson.

The basic principle of HandyJSON is to obtain the characteristics of all attributes from the class information, including the name, the offset of attributes in memory, the number of attributes, the type of attributes, and so on, and then write the value of the data returned by the server into the corresponding memory in the way of operating memory, to achieve json to model. How to use it can go to see the official documentation.

In the fourth part, we use reflection mechanism to retrieve all attribute information from the class Metadata. The most difficult part is to restore the type of the attribute and retrieve the value of the attribute. In the reduction of the time we also use the Swift a C function – swift_getTypeByMangledNameInContext standard library, it is also use this in HandyJson, And HandyJson is this function mapping with @ _silgen_name _getTypeByMangledNameInContext Swift method, then we look at the HandyJson is how to use:

That in combination with the fourth description, the type of the attribute is restored at this time, then the next is to extend through a protocol, using the protocol Metadata, the value of the attribute to obtain.

In fact, this idea also refers to the use of HandyJson, let’s take a look:

HandyJson also uses the AnyExtensions protocol. The extensions(type:) method is the same as the customCast(type:) method. The cast is done directly through the unsafeBitCast function. Let’s look at one more HandyJson method:

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

Pay attention to this this! HandyJson uses the Self keyword to retrieve the value’s true type. This step is similar to the get(from:) method we wrote, but it is better handled.

In fact, I think HandyJson must be written by the authors of the Swift class, structure, enumeration, etc. Internal Metadata structure of special attributes, they also mentioned in the documentation of the library reference library, it is a reflection mechanism to obtain attribute information, Json to Model is much faster than runtime, and performance is much higher than Runtime. Note that it relies heavily on the Metadata structure, which apple will change in the future to make it unusable.