Since Apple launched Swift in 2014, it has gone through several iterations of versions. Swift runs faster, is more efficient, and integrates many high-level language grammar features. This article first understands the similarities and differences between classes and structures and initialization of classes through cases, and analyzes the life cycle of classes through source code.
Introduction to classes and structures
Let’s look at the definitions of classes and structs:
Class ATTeacher {var age: Int var name: String init(age: Int, name: struct) String) { self.age = age self.name = name } }Copy the code
The same
- Defines attributes that store values
- Define methods
- Define subscripts to provide access to their values using subscript syntax
- Define initializers
- use
extension
To expand the function - Follow a protocol to provide a function
The difference between
- Classes have inherited properties that structs do not
- Type conversions enable you to check and interpret the type of a class instance at run time
- A class has a destructor that frees its allocated resources
- Reference counting allows multiple references to a class instance
Legend analysis
Class (class)
Classes are reference types. This means that a variable of a class type does not store the specific instance object directly, but rather refers to the memory address where the specific instance is currently storedAfter variable T is assigned to T1, the figure is as follows:The above is that when class instance T is assigned to T1, they refer to the same memory address.
Structure (struct)
Structs are value types, and structs are very simple to define. Whereas class variables store addresses, value types store concrete instances (or concrete values).
struct ATStudent {
var age: Int
var name: String
}
var s = ATStudent(age: 18, name: Atom)
var s1 = s
Copy the code
In fact, reference types are equivalent to onlineExcel
When we share the link with others, we can see the changes made by others. The value type is equivalent to localExcel
When we put the localExcel
When you pass it on to someone else, you make a copy of it to someone else, and their changes to the content are imperceptible to us.
Code debugging
Class (class)
Through the above definition analysis, it can be concluded thatclass
Is the reference type, andstruct
Is a value type. Let’s verify the analysis with actual code. The definition of theclass
The code is the same as above, then run breakpoint:Initialize theATTeacher
I’m going to assign to t, and I’m going to assign to tt
Assigned tot1
, obtained by printing outt
andt1
The memory address is the same, then change the instancet
In theage
Property value, outputt1
In theage
And look at the result.The results showed thatt1.age
Is equal to the20
This verifies that the class is a reference type and that they refer to the same piece of memory address.
Structure (struct)
Again, change the above class to a structure and run it again:Found that the output is all values, modifys1.age
Before,s
ands1
All values are the same when modifieds1.age
Later,s1
The value is updated, buts
The value of alpha is the same as it was beforestruct
Is a value type.
Memory area concept diagram
Another obvious difference between reference types and value types is where they are stored: typically, value types are stored on the stack and reference types are stored on the heap. Let’s take a look at the common memory region distribution:To verify this, define a function as follows:
func test() {
var age: Int = 10
print(age)
}
test()
Copy the code
inprint
Make a breakpoint and printage
Where tools are used to view variablesage
Storage area in memory.
Storage information for memory areas
- Stack: The context in which local variables and functions are run
- Heap: Stores all objects
- Global: Stores Global variables. Constant; Code section
- Segment & Section: A Mach-o file has multiple segments, each with a different function. Each Section is then divided into smaller sections
- TEXT. TEXT: machine code
- Text. cstring: hardcoded string
- Text. const: initialized constant
- Data. DATA: variable (static/global) DATA initialized. Const: constant that has not been initialized
- Data.bss: uninitialized (static/global) variable
- DATA.common: Symbol declaration that is not initialized
Distribution of classes and structures in memory
Use the frame variable command to see how variables in the structure are distributed in memory, again using the previous code
The help frame variable command is available in helpCopy the code
Breakpoint debug outputage
That’s the first address of the structure,name
inage
increase8
Bytes. So the current structure in memory looks like this:When we initializeATTeacher
“, will request 24 bytes of memory on the stack,age
andname
The addresses in memory are contiguous. After the function is executed, the system will destroy the requested stack memory space, and the stack pointer will be restored to point to the location. Other things being equal, we are instruct
Add aclass
For example, does this affect the distribution of structures in vivo?As you can see, adding classes does not change the originalstruct
Memory affects,ATPerson
Will first allocate memory space on the heap to store variables on the stackp
In the. And again we putstruct
toclass
To run again.It can be seen thatclass
Requested heap memory space.
Class initialization
Current class compilers do not automatically provide member initializers by default, but they do provide default initializers for structures (provided we do not specify initializers ourselves).
struct ATTeacher {
var age: Int
var name: String
}
Copy the code
Specify initializers & convenience initializers
Instances of classes and structures in Swift must be created with an appropriate initial value for all storage properties. So class ATPerson must provide a designated initializer, and we can also provide a convenience initializer for the current class (note: the convenience initializer must call another initializer from the same class).
Class ATPerson{var age: Int var name: String # String) {self.age = age self.name = name} # Convenience init() {self.init(18, "Atom")}}Copy the code
- The specified initializer must ensure that all attributes introduced by its class are initialized before delegating up to the parent class initializer.
- The specified initializer must delegate to the parent initializer before it can set new values for inherited properties. If you do not, the new value assigned by the specified initializer will be overwritten by the initializer in the parent class
- The convenience initializer must first delegate to other initializers in the class before assigning new values to any properties (including those defined in the class). If this is not done, the new value assigned by the convenience initializer will be overwritten by other specified initializers in its class.
- The initializer cannot call any instance methods, read the values of any instance attributes, or refer to self as a value until the first phase of initialization is complete.
The sample
Let’s define a subclass initializer, combining sections 1 and 2 above. Otherwise the compilation will report an error
Initializers can fail
This is easy to understand, which means that the initialization failed because of invalid parameters or external conditions. Such Swift failable initializers write return nil statements to indicate under what circumstances the failable initializers trigger initialization failures. It’s also very simple to write:
class ATAdult { var age: Int var name: String init? (age: Int, name: String) { if age < 18 return nil self.age = age self.name = name } }Copy the code
Necessary initializer
In front of the class initializerrequire
Modifier to indicate that all subclasses of the class must implement the initializerCompile an error if the subclass does not provide one.
Class life cycle
Compilation flowchart
iOS
Develop the language regardlessOC
orSwift
The back end is all throughLLVM
Compiled, as shown below: OC
throughclang
Compiler, compiles toIR
, and then regenerate it into an executable file.o
(This is our machine code),Swift
bySwift
The compiler compiles toIR
, and then generate the executable file. Swift
Compilation process:
Swift
The code bydump parse
Parse into an abstract syntax tree- Generated by semantic analysis
Swift
The middle codeSIL
(SIL includes both native and optimized) - by
LLVM
generateIR
- It is then compiled into machine code from the back-end code (x86, ARM64…)
Swift compile command
Swift-dump-parse # analyze and check the type of output AST swiftc main. Swift-dump-ast # Generate intermediate language (SIL), Swiftc Main. Swift - EMIT - Silgen # Generate Intermediate Language (SIL) Swiftc main. Swift - EMIT -ir # Generate LLVM intermediate language (.bc file) swiftc Swift -emit-assembly # Compile to generate executable. Out file swiftc-o main.o main.swiftCopy the code
SIL code analysis
Start by defining a simple Swift class, and then generate the intermediate SIL code as follows:
class ATTeacher {
var age: Int = 18
var name: String = "Atom"
}
var t = ATTeacher()
Copy the code
Generate intermediate code SIL commands:
Swiftc-emit - SIL main.swift >./main.silCopy the code
Open the generated main.sil file and intercept some of the code
class ATTeacher { @_hasStorage @_hasInitialValue var age: Int { get set } @_hasStorage @_hasInitialValue var name: String {get set} @objc deinit init()} // main entry function // 0%~9% : registers that can be understood as SIL (virtual), and eventually run on the device will be mapped to the real register sil@main: $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 { bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>): // alloc_global: // s4main1tAA9ATTeacherCvp: Alloc_global @$s4main1tAA9ATTeacherCvp // id: alloc_global @$s4main1tAA9ATTeacherCvp %3 = global_addr @$s4main1tAA9ATTeacherCvp: $*ATTeacher: %7 %4 = metatype $@thick ATTeacher.Type // user: // function_ref atteacher.__allocating_init () // get the __allocating_init() pointer address %5 = function_ref @$s4main9ATTeacherCACycfC : $@convention(method) (@thick ATTeacher.Type) -> @owned ATTeacher // user: %6 // Use a 5% function pointer with metatype and return the result to 6% %6 = apply %5(%4) : $@convention(method) (@thick ATTeacher.Type) -> @owned ATTeacher // user: Store %6 to %3: $*ATTeacher // id: Int32, 0; // user: %9 = struct $Int32 (%8: struct $Int32) $Builtin.Int32) // user: %10 return %9 : $Int32 // id: %10 } // end sil function 'main'Copy the code
Detailed instructions on Swift SIL can be found in the official documentation.
There is a pointer to function_ref corresponding to s4main9ATTeacherCACycfC in the main function above. A search in the SIL file will locate the following functions:
// atteach.__allocating_init () // atteach.type: isa sil hidden [exact_self_class] @$s4main9ATTeacherCACycfC: $@convention(method) (@thick ATTeacher.Type) -> @owned ATTeacher { // %0 "$metatype" bb0(%0 : $@thick ATTeacher.Type): %1 = alloc_ref $ATTeacher // user: %3 // function_ref ATTeacher.init() %2 = function_ref @$s4main9ATTeacherCACycfc : $@convention(method) (@owned ATTeacher) -> @owned ATTeacher // user: %3 %3 = apply %2(%1) : $@convention(method) (@owned ATTeacher) -> @owned ATTeacher // user: %4 return %3 : $ATTeacher // id: %4 } // end sil function '$s4main9ATTeacherCACycfC'Copy the code
To viewalloc_ref
Official documentation of the explanation alloc_ref
In fact, it is to request memory space from the heap, if marked asobjc
The will toObjective-C
The initialization method of.
The sample analysis
In the initializationATTeacher
Make a break point,debug
Opened in assembly mode, you can see that the initialization isswift_allocObject
Then put theATTeacher
Integration fromNSObject
Run it again and you can see that the initialization mode passesObjective-C
The way.
class ATTeacher: NSObject {
var age: Int = 18
var name: String = "Atom"
}
var t = ATTeacher()
Copy the code
Swift source code analysis
Through the topSIL
The code runs in conjunction with the instance breakpointSwift
We saw that when we initialized the functionswift_allocObject
, next downloadSwift
The source code, through the global search can be locatedHeapObject.cpp
The file has the following methods: swift_allocObject
Call the_swift_allocObject_
._swift_allocObject_
Call againswift_slowAlloc
, and then global searchswift_slowAlloc
You can see its function definition:You can see the call heremalloc
Basically the same asObjective-C
Similar to the initialization of themalloc
To open up memory space.
The following points can be made from the above analysis:
_allocating_init
~ >swift_allocObject
~ >_swift_allocObject_
~ >swift_slowAlloc
~ >malloc
Swift
Object memory structureHeapObject(OC objc_object)
, has two properties: one isMetadata
, one isRefCount
, which occupies 16 bytes by default.
Through the source to locate the HeapObject, HeapMetadata found in the HeapObject structure, according to the namespace and find TargetHeapMetadata;
using HeapMetadata = TargetHeapMetadata<InProcess>;
Copy the code
Find the following definition from TargetHeapMetadata:
template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
using HeaderType = TargetHeapMetadataHeader<Runtime>;
TargetHeapMetadata() = default;
constexpr TargetHeapMetadata(MetadataKind kind)
: TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
: TargetMetadata<Runtime>(isa) {}
#endif
};
Copy the code
According to the function definition, if it’s a pure Swift class it’s Metadata, if it’s interacting with objC it’s ISA, it’s pure Swift code and there’s objC code that generates different data types. The definition of kind in the source code is shown in the following table:
Name | Value |
---|---|
Class | 0x0 |
Struct | 0x200 |
Enum | 0x201 |
Optional | 0x202 |
ForeignClass | 0x203 |
Opaque | 0x300 |
Tuple | 0x301 |
Function | 0x302 |
Existential | 0x303 |
Metatype | 0x304 |
ObjCClassWrapper | 0x305 |
ExistentialMetatype | 0x306 |
HeapLocalVariable | 0x400 |
HeapGenericLocalVariable | 0x500 |
ErrorObject | 0x501 |
LastEnumerated | 0x7FF |
According to thekind
We found it.getTypeContextDescriptor
Function definition of:ifgetKind
Type isClass
, will be strong intoTargetClassMetadata
Type, according toTargetClassMetadata
We can guess at the definition ofSwift
The data structure of the class would look something like this:
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
The above is the result of the source code, the following example to verify that is not the case
The sample analysis
We can bind a pointer to a class to a custom HeapObject structure as follows:
struct HeapObject { var metadata: UnsafeRawPointer var refcounted1: UInt32 var refcounted2: UInt32 } class ATTeacher { var age: Int = 18 var name: String = "Atom"} var t = ATTeacher() let objcRawPtr = Unmanaged. PassUnretained (t as AnyObject).toopaque () let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1) print(objcPtr.pointee)Copy the code
Then run:Metadata (HeapObject); metadata (HeapObject); metadata (HeapObject);
struct HeapObject { var metadata: UnsafeRawPointer var refcounted1: UInt32 var refcounted2: 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 } class ATTeacher { var age: Int = 18 var name: String = "Atom"} var t = ATTeacher() let objcRawPtr = Unmanaged. PassUnretained (t as AnyObject).toopaque () // Let objcPtr = objcRawPtr. BindMemory (to: heapObject. self, capacity: HeapObject. 1) print(objcptr.pointee) // Bind the pointer to objcptr.pointee. Metadata to the metadata structure type. // MemoryLayout objcPtr.pointee.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee print(metadata)Copy the code
Run againThe code examples also verify that the data structure of Swift class is the Metadata obtained from the above source code analysis.
conclusion
You learned about classes and structures, class initialization, and class life cycles in Swift.
- Similarities and differences in classes and structures
- Concepts and usage of various initializers for class
- The compilation process of the class is analyzed through SIL
- Through source code analysis of the structure of the class and through the example to verify