“This is the third day of my participation in the First Challenge 2022, for more details: First Challenge 2022”.

  • This article mainly introduces the life cycle analysis of classes

We know that in iOS, app development compiles our code into machine-recognized code mach-O files through the compiler, which is divided into front-end compiler and back-end compiler. The front-end compiler is CLang in OC, and the SWIFT compiler is Swift. Through the front-end compiler for lexical analysis, syntax analysis, check whether the syntax is correct to generate intermediate code IR, code optimization through the middle layer, to the back-end LLVM for processing, generate Mach-O file

The process is as follows:

You can refer to my previous article for the specific processLLVM process

Let’s take a look at the command for the front-end compiler swift, similar to the command statement for clang in oc

Swiftc main. Swif-dump-parse // Analyze and check the type of output AST swiftc main. Swif-dump-ast // generate intermediate language (SIL), Swiftc main. Swift - EMIT - Silgen // Generate intermediate Language (SIL) Swift-emit -sil // Generates LLVM intermediate language (.ll file) swifTC main. Swift-emit -ir // generates LLVM intermediate language (.bc file) swifTC Swift -emit-assembly // Compilation generates executable. Out file swiftc-o main.o main.swiftCopy the code

1. SIL file analysis

Create a new CommandLineTool project and define a Person class in main

The following code is generated by typing at the terminalsilThe file is generated and printed at the terminalmain.silfile

swiftc main.swift -emit-sil
Copy the code

The person above is our class, with the get and set methods for attributes, and the deinit and init methods. @main is the entry function.

 

We can generate the main.sil file in the current directory by using >

swiftc main.swift -emit-sil > ./main.sil
Copy the code
// main is equivalent to the entrance of main in oc sil@main: $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 { bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>): alloc_global @$s4main1pAA6PersonCvp // id: %2 Assign a person global variable %3 = global_addr @$s4main1pAA6PersonCvp: $* person // user: %4 = metatype $@thick person. Type // user: %6 %4 Assign a meta Type to person.type // function_ref person.__allocating_init ()// execute the init method function %5 = function_ref @$s4main6PersonCACycfC : $@convention(method) (@thick Person.Type) -> @owned Person // user: %6 = apply %5(%4) : $@convention(method) (@thick person.type) -> @owned Person // user: %6 store %6 to %3: $*Person // id: Person %8 = integer_literal $builtin.int32, 0 // Person %8 = integer_literal $builtin.int32, 0 // user: Struct $Int32 (% 2: $builtin.int32) // struct $Int32 (% 2: $builtin.int32) // struct $Int32 (% 3: $builtin.int32) // struct $Int32 (% 3: $builtin.int32) // $Int32 // id: %10 } // end sil function 'main'Copy the code

The above %0, %1, etc are virtual registers. In the SIL file, you’ll see a lot of keywords you don’t understand. You can check the official documentation on GitHub

  • Script compilation open

We can also add script to run open 1. Add target

addRun the script

Add the script in the folder where main is currently located

Swiftc - emit - ${SRCROOT} sil project folder / * * / main. Swift | xcrun swift - demangle >. / main sil && open main. SilCopy the code

If you get an error, or you can’t open it, you can open it firstvscodeTo set upmain.silThe opening mode of

Compile analysis process

We use assembly for analysis

We break the point before the Person class is initialized, and then in__allocating_init()At break point

Setp into the current method, hold down control and click enter

Once inside, the init method is called to initialize it

Enter the init

3. Source code analysis

Let’s take a look at the source code. Source code can go to apple’s official website -swift source download address. Use VSCode to open the downloaded Swift source code and search swift_allocObject globally.

Just like the implementation of _swift_allocObject_, we pass three parameters, create memory space with swift_slowAlloc based on the allocated memory size, then associate the metadata with the memory space, and finally return the instance object.

Click swift_slowAlloc to view

Alignment Default size

Above we end up generating an object of heapObject, just like I did in OC class inheritance with objC_class

  • HeapObject

There are two main initialization methods, each containing two parameters: HeapMetadata metaData and InlineRefCounts refcounts

  • HeapMetadata

Check the TargetHeapMetadata

It is compatible with classes in OC, and the data element is ISA. The swift class defaults to MetaDataKind data

To view#include "MetadataKind.def"Get the corresponding value

name Value Class 0x0 Struct 0x200 Enum 0x201 Optional 0x202 ForeignClass 0x203 ForeignClass 0x203 Opaque 0x300 Tuple 0x301 Function 0x302 Existential 0x303 Metatype 0x304 ObjCClassWrapper 0x305 ExistentialMetatype 0x306 HeapLocalVariable  0x400 HeapGenericLocalVariable 0x500 ErrorObject 0x501 LastEnumerated 0x7FFCopy the code

Continue to look at TargetMetadata inherited from TargetHeapMetadata, which structures can inherit from in C++

So if you look at TargetMetadata there’s a bunch of method attributes, so let’s focus on the methods of getTypeContextDescriptor

When kind is a Class, we get a pointer to TargetClassMetadata. If kind is Class, we get a pointer to TargetClassMetadata.

Inherit TargetAnyClassMetadata and click to view it

If it is an OC class, the TargetAnyClassMetadata structure is similar to the structure of our OC class isa, superClass, Cache, bits (data).

So let’s summarize the data structure of SWIFT

struct Metadata {
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
Copy the code

From the previous analysis, we know that the initialization generates a heapObject, which we can customize according to this structure

class Person{ var name = "fish" var age = 3 } struct HeapObject{ var metadata: UnsafeRawPointer var refCounts: UInt32 } struct Metadata{ var kind: Int var superClass: Any.Type var cacheData: (Int, Int) var data: Int var classFlags: Int32 var instanceAddressPoint: UInt32 var instanceSize: UInt32 var instanceAlignmentMask: UInt16 var reserved: UInt16 var classSize: UInt32 var classAddressPoint: UInt32 var typeDescriptor: UnsafeMutableRawPointer var iVarDestroyer: UnsafeRawPointer} let p = Person() let objcRawPtr = unmanaged.passunretained (p as AnyObject).toopaque ()// Obtain the pointer let of the instance object objcPtr = objcRawPtr.bindMemory(to: HeapObject.self , capacity: 1)// Convert HeapObject to our custom MetaData structure print(objcptr.pointee);Copy the code

Let’s use LLDB to verify

A metadata class is essentially a structure

4. To summarize

  1. The nature of the class in Swift isheapObjectThe structure of the type containsmetadataandReference countingThe nature of the class in oc isobjc_classType of structure.
  2. The instance object in Swift storesmetadataandReference counting.Member variables. The instance variable in oc stores isisaandMember variables
  3. The swift,metadataContains information about the class throughgetTypeContextDescriptorGet the type of the description and store the data in the corresponding structure.
  4. A flowchart