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:
- Create a class Animal and get it from OC RunTime
Class 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:
- To the class properties and methods
@objc
The 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
- Suppose the current class inherits from
NSObject
class Animal: NSObject {
var age: Int = 18
func eat(){
print("eat")
}
}
Copy the code
The run result shows that only the band is retrievedinit
methods
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
- The current Swift class inherits from
NSObject
, and properties and methods are used@objc
modified
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:
-
For a pure Swift class, methods and properties are without any modifiers. The Runtime feature is no longer available at this point.
-
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.
-
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.
-
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.
-
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:
-
Retrieves the Metadata of the current struct, and then converts it to StructMetaData
-
Gets the name of the structure by accessing the property memory
-
Gets the number of attributes
-
Gets the description of an attribute
-
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/…