preface

In Objective-C, I believe that every iOS developer knows Runtime. Now that Swift has updated to version 5.6, there is a question in learning Swift: Does Swift have Runtime?

What is the Runtime

Objective-c is a dynamic language, which means it requires not only a compiler, but also a Runtime system to dynamically create classes and objects, do messaging, and forward, features that come from the Runtime. Swift is statically typed, and metadata types allow code to examine and manipulate arbitrary values at run time. How is this implemented? Through the following contents:

Does Swift have a Runtime feature?

Here is an example to verify:

  1. Create a class Animal and get it from OC RunTimeClass properties and methods
class Animal: NSObject { var age: Int = 2 func eat(){ print("eat") } } func test(){ var methodCount:UInt32 = 0 let methodlist = class_copyMethodList(Animal.self, &methodCount) for i in 0.. <numericCast(methodCount) { if let method = methodlist? [i]{ let methodName = method_getName(method); Print (" describing: methodName) \(String(describing: methodName) ")} else {print("not found method"); } } var count:UInt32 = 0 let proList = class_copyPropertyList(Animal.self, &count) for i in 0.. <numericCast(count) { if let property = proList? [i]{ let propertyName = property_getName(property); Print (" property member property :\(String(utf8String: propertyName)!) ")} else {print(" the attribute you want is not found "); }} print(" call this method ")} test()Copy the code

No properties or methods were obtained after the run:

  1. To the class properties and methods@objcThe keyword
class Animal: NSObject {
    @objc var age: Int = 2
    @objc func eat(){
        print("eat")
    }
}
Copy the code

After the run, the properties and methods are obtained

  1. Suppose the current class inherits fromNSObject
class Animal: NSObject {
    var age: Int = 18
    func eat(){
        print("eat")
    }
}
Copy the code

The run result shows that only the band is retrievedinitmethods

We can also see the information that the current Swift class inherits from NSObject that the Swift class exposes to the OC

Turns out there really is only one init method

  1. The current Swift class inherits fromNSObject, and properties and methods are used@objcmodified
class Animal: NSObject {
    @objc var age: Int = 2
    @objc func eat(){
       print("eat")
    }
}
Copy the code

You can see the results of the run, print and properties and methods

Viewing does expose properties and methods, which results in properties and methods being obtained through the Runtime API. You can imagine that the Hooks and association properties of the Runtime are also not used by Swift.

From the above examples, it can be concluded as follows:

  1. For a pure Swift class, methods and properties are without any modifiers. The Runtime feature is no longer available at this point.

  2. For pure Swift classes, methods and properties with @objc flags are currently available via the Runtime API, but we can’t schedule them in OC.

  3. For classes that inherit from NSObject, if we want to get the current properties and methods dynamically, we must add the @objc keyword before they are declared, otherwise there is no way to get them through the Runtime API.

  4. A pure SWIFT class does not have dynamic, but dynamic can be obtained by adding the dynamic modifier before methods and attributes. The Swift class that inherits from NSObject has dynamic methods, and other custom methods and properties that want to be dynamic need to add the dynamic modifier.

  5. Dynamic modifier cannot be added to method whose parameter or attribute type is swift specific and cannot be mapped to Objective-C (e.g. Character, Tuple)

Swift class does not have Runtime features. How do I get properties and methods?

Mirror reflection mechanism

Reflection is a feature that dynamically retrieves type, member information, and calls methods, properties, and so on 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.

Swift’s reflection mechanism is based on a structure called Mirror. Create a Mirror object for a specific instance that you can then query.

Simple use of Mirror

Let animal = animal () // Create a Mirror instance by using the constructor (Any), which means it can be a class, structure, enumeration, etc. The result is an instance that provides information about the set 'Children' of this value. Let mirror = mirror (reflecting: For pro in mirror.children{// Then we can print the current name directly through the label, Print ("\(pro.label):\(pro.value)")}Copy the code

If we did not use Mirror, the printed instance would look like this, with no specific attribute information

Based on the Mirror instance created above, follow through to see the corresponding definition

Public let children: Mirror.Children //Children is a collection type, Public TypeAlias Children = AnyCollection< mirror. Child> // The generic argument is a tuple type public TypeAlias Child = (label: String? , value: Any)Copy the code

Mirror source code

The bottom layer of the Swift runtime is implemented in C++, but there is no direct access to C++ classes in Swift, so there is a C connection layer. Swift implementation of reflection is in reflectionmirror. Swift and C++ implementation is in reflectionmirror.cpp.

Swift: Mirror.swift: mirror.swift: mirror.swift: mirror.swift: mirror.swift: mirror.swift: mirror.swift: mirror.swift

As you can see, it takes an argument of type Any, and it also has an if case to determine if the subject is in compliance with the customReflectable protocol. If it is, we call the customMirror directly, otherwise we call the subordinate functions.

There are two important points to note here: the if case is written to enumerate the pattern matching of cases, just like our Switch statement, which is a Switch statement with only one case. At the same time there is a customRefletable protocol.

The specific usage of customRefletable

First we follow the customReflectable protocol and implement the property customMirror, which returns a Mirror object. The code implementation is as follows:

class Animal: 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
    }
}

var animal = Animal(age: 2, name: "pig")
Copy the code

Run it. Print Animal

Normally you can’t print out a message

This is the initialization method of the Mirror structure

If this protocol is not followed, the following approach is used. Global search for internalReflecting, and then internalReflecting is found in reflectionMirror. swift.

Gets the type of the current subject

let subjectType = subjectType ?? _getNormalizedType(subject, type: type(of: subject))
Copy the code

Global search _getNormalizedType, found in reflectionMirror.cpp file

getNormalizedType

(_: T, type: Any.type) -> any. Type finally calls the C++ code in reflectionmirror. CPP, which uses the compiler field @_silgen_name, which is actually a hidden symbol for Swift. The function maps a C/C++ function directly to a Swift function.

The call implementation wasn’t as exciting as it might have been. Basically a big switch declaration and some extra code to handle special cases. The important thing is that it ends the call to F with a subclass instance of ReflectionMirrorImpl, and then calls the methods on that instance to get the real work done. Then follow the ReflectionMirrorImpl

ReflectionMirrorImpl (ReflectionMirrorImpl); ReflectionMirrorImpl (ReflectionMirrorImpl); This is an implementation of the common class, struct, enum, Tuple types.

The reflection of the structure

  • The first is a helper method to check that the structure fully supports reflection. The structure metadata stores such an accessible flag bit. As in the tuple code above, we know that type is actually a StructMetadata pointer, so we can pass it freely:
struct StructImpl : ReflectionMirrorImpl { bool isReflectable() { const auto *Struct = static_cast<const StructMetadata *>(type); const auto &Description = Struct->getDescription(); return Description->isReflectable(); }}Copy the code
  • The display style for the structure is S:
char displayStyle() override {
    return 's';
  }
Copy the code
  • The number of child elements is the number of fields given by the metadata, or 0 (if the type does not actually support reflection)
intptr_t count() override { if (! isReflectable()) { return 0; } auto *Struct = static_cast<const StructMetadata *>(type); return Struct->getDescription()->NumFields; }Copy the code
  • As before, the subscript method is the tricky part. It starts similarly, doing boundary checking and finding offsets:
intptr_t childOffset(intptr_t i) override {
    auto *Struct = static_cast<const StructMetadata *>(type);
    if (i < 0 || (size_t)i > Struct->getDescription()->NumFields)
      swift::crash("Swift mirror subscript bounds check failure");
    // Load the offset from its respective vector.
    return Struct->getFieldOffsets()[i];
  }
Copy the code
  • Get the type information with _swift_getFieldAt, and once it has field information, everything will behave like the code for the corresponding part of the tuple. Fill in the name and calculate the pointer stored in the field:
const FieldType childMetadata(intptr_t i, const char **outName, void (**outFreeFunc)(const char *)) override { StringRef name; FieldType fieldInfo; std::tie(name, fieldInfo) = getFieldAt(type, i); assert(! fieldInfo.isIndirect() && "indirect struct fields not implemented"); *outName = name.data(); *outFreeFunc = nullptr; return fieldInfo; }Copy the code

Read Struct structure source code

Let’s take a look at how the Mirror retrieves this data using structs. Of course, the number of attributes (as can be seen from the Metadata getDescription query field NumFields)

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

The help function for swift_getField looks up field descriptors for a given type

Metadata, getDescription(), fielddescription (), and description of the current type of Metadata are used to describe the current type of properties. So we can see how the Mirror works. While we’ve explored classes, structures, and enumerations, we’ll see if we can use Metadata ourselves to retrieve various properties of the current type.

Struct code implementation

TargetStructMetadata structure

Open Swift source code, locate metadata. h file, search TargetStructMetadata, and then follow up the location structure step by step, here is a sorted out flow chart, very clear to see the various structures:

Convert the above flowchart into pseudocode:

Struct StructMetaData{var kind: Int32 // Inherit var typeDescriptor from TargetValueMetadata: UnsafeMutablePointer<StructDescriptor>} struct StructDescriptor {//Flags, Parent inherits // the flag describing the context from TargetContextDescriptor, Let flags: Int32 // let flags: Int32 // let flags: Int32 // RelativePointer<CChar> var AccessFunctionPtr: RelativePointer<UnsafeRawPointer> RelativePointer<FieldDescriptor> // The number of attributes stored in the structure var NumFields: Int32 // The offset of the attribute's field offset vector in the metadata var FieldOffsetVectorOffset: Struct FieldDescriptor {var MangledTypeName: RelativePointer<CChar> var Superclass: RelativePointer<CChar> var kind: UInt16 var fieldRecordSize: Int16 var numFields: Var fields: FieldRecord} struct FieldRecordT<Element> {var Element: Element mutating func element(at i: Int) -> UnsafeMutablePointer<Element> { return withUnsafePointer(to: &self) { return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: Struct FieldRecord {var Flags: Int32 var MangledTypeName: RelativePointer<CChar> var FieldName: RelativePointer<CChar>} RelativeDirectPointer struct RelativePointer<T> {var offset: Int32 // offset position, Mutating func get() -> UnsafeMutablePointer<T>{let offset = self.offset //withUnsafePointer UnsafeMutablePointer Returns a pointer to an object of type T // UnsafeRawPointer converts the p pointer to an unknown type // advanced performs memory offset // numericCast converts offset to offset units // Sumingmemorybound: T return withUnsafePointer(to: &self) {p in return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self)) } } }Copy the code

Parse custom structs

We can parse a custom Struct based on the restored code

struct Animal { var age: Int = 2 var name: String = "pig"} //UnsafeMutablePointer<StructMetaData>. Self fetches the Metadata of the current struct StructMetaData let PTR = unsafeBitCast(animal. self as any. Type, to: UnsafeMutablePointer<StructMetaData>. Self) Get the name field memory address let namePtr = PTR. Pointee. TypeDescriptor. Pointee. Name. The get () / / the String function output, Print ("current class name: \(String(cString:) NamePtr)) ") / / get the number of attributes let numFields = PTR. Pointee. TypeDescriptor. Pointee. NumFields print (" the number of the current class attribute: \ (numFields) ") / / attribute description information let fieldDespritor = PTR. Pointee. TypeDescriptor. Pointee. Fields. The get () / / traverse properties for I 0. In the. <numFields {let record = withUnsafePointer(to: &fieldDespritor.pointee.fields){ return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Fieldrecord.self).advanced(by: numericCast(I)))} Record. The pointee. FieldName. The get ()) / / read the type of the property let manNameStr = String (cstrings: Record the pointee. MangledTypeName. Get the print ()) (" type name: \ (recordNameStr) - type: \ (manNameStr) ")}Copy the code

Run to see that you did get the Struct name, the property name, and the property type

Si is Int, SS is String. Struct Metadata and Enum data structure Metadata structure is similar, and the implementation of the Class is more difficult.

Conclusion:

Combined with the whole is divided into these steps:

  1. Retrieves the Metadata of the current struct, and then converts it to StructMetaData

  2. Gets the name of the structure by accessing the property memory

  3. Gets the number of attributes

  4. Gets the description of an attribute

  5. Read the information for the property

Thank you very much for the flow chart of students finishing: www.jianshu.com/p/30dc12515…

Reference article: Swift.gg /2018/11/15/…