First, structure
In Swift’s standard library, the vast majority of exposed types are structures, while enumerations and classes make up only a small portion. Common types such as Bool, Int, Double, String, Array, and Dictionary are all structures.
We now define a structure.
struct SHPerson {
var age: Int
var name: String
}
let p = SHPerson(age: 18, name: "Coder_ zhang SAN")
Copy the code
All constructs have an initializer (initializer, initializer, constructor, constructor) automatically generated by the compiler. In the code above, you can pass in all member values to initialize all members (Stored properties, Stored properties).
1. Initializer for structure
The compiler may generate more than one initializer for a structure, depending on the situation. The premise is to ensure that all members have initial values.
2. Customize initializers
Once you customize an initializer when defining a structure, the compiler does not help automatically generate other initializers.
struct SHPerson {
var age: Int
var name: String
init(age: Int) {
self.age = age
self.name = "Coder_ zhang SAN"}}let p = SHPerson(age: 0)
Copy the code
When we initialize a structure, we must ensure that all the members of the structure are valued, so when we set the initial value of a member variable of the structure, the generated initializer can be assigned without passing the parameter of the member variable.
3. The memory structure of the structure
SHPerson has age, weight, and sex members.
struct SHPerson {
var age: Int
var weight: Int
var sex: Bool
}
print(MemoryLayout<SHPerson>.size) / / 17
print(MemoryLayout<SHPerson>.stride) / / 24
print(MemoryLayout<SHPerson>.alignment) / / 8
Copy the code
It prints the number of aligned bytes and the amount of memory it occupies. On 64-bit systems, Int is 8 bytes and Bool is 1 byte, so SHPerson is 17 bytes, but since memory alignment rules are followed (8 bytes), So the system allocates 24 bytes to store the SHPerson.
Second, the class
The definition of a class is similar to that of a structure, but the compiler does not automatically generate initializers for classes that can pass in member values.
1. Class initializer
1.1 Specifying an initializer
When a member of a class has no initial value, you must define a custom initializer to initialize the member value.
class SHPerson {
var age: Int
var name: String
init(age: Int.name: String) {
self.age = age;
self.name =name; }}let p = SHPerson(age: 18, name: "Coder_ zhang SAN")
Copy the code
If all members of a class have specified initializers when they are defined, the compiler generates an initializer for the class with no arguments, and members are initialized in this initializer.
class SHPerson {
var age: Int = 18
var name: String = "Coder_ zhang SAN"
}
let p = SHPerson(a)Copy the code
1.2 Failable initializer
We need to return nil to the initializer if it doesn’t meet a condition, so we can add an option to init.
class SHPerson {
var age: Int
var name: String
init?(age: Int.name: String) {
if age < 18 { return nil}
self.age = age
self.name = name
}
}
let p1 = SHPerson(age: 16, name: "Coder_ zhang SAN")
let p2 = SHPerson(age: 18, name: "Coder_ li si")
print("p1 - \(String(describing: p1))")
print("p2 - \(String(describing: p2))")
Copy the code
Print result: P1- nil
p2 - Optional_1_ struct and class.SHPerson)
Copy the code
For example, SHPerson returns nil when it is less than 18 years old, belonging to a minor.
1.3 Necessary initializers
Required initializers need to be decorated with required before init.
class SHPerson {
var age: Int
var name: String
// The parent class defines the initializer that must be implemented
required init(age: Int.name: String) {
self.age = age
self.name = name
}
}
class SHStudent: SHPerson {
var height: Int
init(height: Int) {
self.height = height
super.init(age: 18, name: "Coder_ zhang SAN")}// Subclasses must implement the necessary initializers for their parent class
required init(age: Int.name: String) {
fatalError("init(age:name:) has not been implemented")}}Copy the code
As the code shows, when you decorate required before init, all subclasses of the class must implement the initializer.
1.4 Convenient initializer
We can provide a convenience initializer for a class that needs to be modified by convenience before init.
class SHPerson {
var age: Int
var name: String
init(age: Int.name: String) {
self.age = age
self.name = name
}
convenience init(a) {
self.init(age: 18, name: "Coder_ zhang SAN")}}Copy the code
As the code shows, the convenience initializer must call another initializer from the same class, and must eventually call a designated initializer.
The essential difference between structure and class
The essential difference between a structure and a class is that a structure is a value type and a class is a reference type. One of the most obvious differences between them is where they are stored: typically, value types are stored on the stack and reference types are stored on the heap.
class SHPerson {
var age = 18
var height = 180
}
struct SHPoint {
var x = 0;
var y = 0;
}
func test(a) {
let point = SHPoint(a)let person = SHPerson()}Copy the code
After SHPoint is initialized and assigned to point, SHPoint’s memory data is placed directly on the stack. After SHPerson is initialized and assigned to person, person is just a reference address. The memory data stored in this address is SHPerson’s memory address, which is placed in the heap space.
Value types
When a value type is assigned to a var, let, or function, it copies everything directly. This operation is similar to copying or paste a file to create a new copy of the file. It is a deep copy.
struct SHPoint {
var x = 4;
var y = 8;
}
var p1 = SHPoint(a)var p2 = p1;
p2.x = 6
print("p1 - \(p1)")
print("p2 - \(p2)")
Copy the code
Print result: P1- SHPoint(x: 4, y: 8)
p2 - SHPoint(x: 6, y: 8)
Copy the code
We can see that after modifying x of P2, there is no effect on P1, which is a deep copy. Let’s look at the print of the array.
var a1 = [1.2.3]
var a2 = a1
a2.append(4)
a1[0] = 2
print(a1)
print(a2)
Copy the code
Print result: [2.2.3]
[1.2.3.4]
Copy the code
In the Swift standard library, in order to improve performance, String, Array, Dictionary, and Set adopt the technology of Copy On Write, for example, only when there is a “Write” operation, the actual Copy operation will be performed.
Swift ensures optimal performance for assignment operations of library value types, so there is no need to avoid assignment in order to ensure optimal performance.
Suggestion: If you do not need to modify it, try to define it as let.
5. Reference types
When a reference is assigned to a var, let, or function, it makes a copy of the memory address. Similar to making a double of a file (shortcut, link) that points to the same file. It is a shallow copy.
class SHPerson {
var age: Int = 18
var name: String = "Coder_ zhang SAN"
}
let p1 = SHPerson(a)let p2 = p1
print("p1-age: \(p1.age)")
p2.age = 20
print("p1-age: \(p1.age)")
print("p2-age: \(p2.age)")
Copy the code
Print result: P1-age: 18
p1-age: 20
p2-age: 20
Copy the code
Object heap space application process:
In Swift, to create an instance object of class, to allocate memory to the heap space, the process is as follows:
- Class.__allocating_init()
- libswiftCore.dylib: swift_allocObject
- libswiftCore.dylib: swift_slowAlloc
- libsystem_malloc.dylib: malloc
The malloc function on Mac and iOS always allocates a multiple of 16.
class_getInstanceSize
: Returns the size of the class instance.malloc_size
: Specifies the memory size allocated by the system.
class CGPoint {
var x = 11
var y = 22
var test = true
}
var p = CGPoint(a)print(class_getInstanceSize(CGPoint.self))
print(malloc_size(unsafeBitCast(p, to: UnsafeRawPointer.self)))
Copy the code
Print result:40
48
Copy the code
-
According to the print, the size of CGPoint is 40 bytes, and the memory size of CGPoint allocated by the system is 48 bytes.
-
In CGPoint, x is 8 bytes, Y is 8 bytes, and test is 1 byte, so we are currently looking at 17 bytes. But because the class is stored in heap space, it is preceded by 8 bytes for the type information, 8 bytes for reference counting, and the faces, which adds up to 33 bytes. According to memory alignment rules (8 bytes), cgPoints are 40 bytes in size.
-
Since the malloc function in Mac and iOS always allocates a multiple of 16, the system eventually allocates 48 bytes of CGPoint memory.
Structure and class selection
Structs and classes are used in a similar way, so is it better to use structs or classes in normal development? In this case, if the defined data structure is relatively simple, it is recommended to use the structure, such as Model. Classes are recommended for more complex data structures, such as polymorphisms.
- The structure’s memory is allocated in stack space, and when the structure runs out, the memory is automatically freed without any additional processing.
- Class memory allocation in the heap space, the system needs to allocate the memory size of the class and other operations such as destruction, relative to the structure, the performance of the cost.
StructVsClassPerformance Demo test:
We can visually test the time allocation of current structures and classes with the demo StructVsClassPerformance on Github.
The specific code will not be posted, let’s see how to call and print the result:
Tests.runTests()
Copy the code
Print result:Running tests
class (1 field)
9.566281178005738
struct (1 field)
6.391943185008131
class (10 fields)
10.430800677015213
struct (10 fields)
6.610909776005428
Copy the code
As can be seen intuitively from the printed results, the structure is nearly twice as fast as the time allocation of the class.
Vii. Swift Principle expansion
1. Swift code compilation process
Both OC and Swift are developed for iOS, and the backend is compiled by LLVM, as shown below:
OC is compiled by the CLang compiler into IR, which is then regenerated into the executable.o(in this case our machine code). Swift is compiled into IR by Swift compiler and then generates executable files.
- First of all,
Swift Code
after-dump-parse
Carry out semantic analysis and parse intoParse
(Abstract syntax tree). Parse
after-dump-ast
Perform semantic analysis to check whether the syntax is correct and safe.Seam
After theSwift Code
Will degrade toSILGen
(Swift intermediate code) forSILGen
Subdivided into native (Raw SIL
) and optimized (SIL Opt Canonical SIL
).- optimized
SIL
Will be downgraded from LLVM toIR
The downgrade intoIR
The backend code is then compiled into machine code.
The above is the Swift compilation process, and the following is the command for the compilation process.
Analysis output AST:
// Analyze the output AST
swiftc main.swift -dump-parse
// Analyze and check the type output AST
swiftc main.swift -dump-ast
// Generate intermediate language (SIL), not optimized
swiftc main.swift -emit-silgen
// Generate intermediate language (SIL), optimized
swiftc main.swift -emit-sil
// Generate LLVM intermediate language (.ll file)
swiftc main.swift -emit-ir
// Generate LLVM intermediate language (.bc file)
swiftc main.swift -emit-bc
// Generate assembly
swiftc main.swift -emit-assembly
// Compile to generate an executable.out file
swiftc -o main.o main.swift
Copy the code
Compile the following code into SIL code:
import Foundation
class SHPerson {
var age = 18
var name = "Coder_ zhang SAN"
}
let p = SHPerson(a)Copy the code
Go to the main. Swift directory of the project and type swiftc main. swift-emit -sil and press Enter. A main. The SIL code is as follows:
The syntax of SIL is also documented. Here is the address of the documentation: SIL reference document
2. Compile the initialization process of the exploration class
Next, we can look at the initialization process of the class through assembly. We can break the following:
Next, turn on assembly debugging.
When SHPerson is initialized, the underlying function __allocating_init is called. What does __allocating_init do?
Let the breakpoint go to the line __allocating_init, hold down the Control key, and click the down button.
As you can see, the internal implementation of __allocating_init calls a swift_allocObject function and gets lost while continuing with assembly.
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.
Find the implementation of the swift_allocObject function in the heapObject. CPP file, and above the implementation of the swift_allocObject function, there is an implementation of the _swift_allocObject_ function.
// The first argument, metadata.
// The size of the allocated memory
// The third argument, memory alignment, is usually 7 because 8-byte alignment is observed
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
assert(isAlignmentMask(requiredAlignmentMask));
auto object = reinterpret_cast<HeapObject * >(
swift_slowAlloc(requiredSize, requiredAlignmentMask));
// NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
// check on the placement new allocator which we have observed on Windows,
// Linux, and macOS.
new (object) HeapObject(metadata);
// If leak tracking is enabled, start tracking this object.
SWIFT_LEAKS_START_TRACKING_OBJECT(object);
SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);
return object;
}
Copy the code
A swift_slowAlloc function is called inside the swift_slowAlloc function.
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
void *p;
// This check also forces "default" alignment to use AlignedAlloc.
if (alignMask < = MALLOC_ALIGN_MASK) {
#if defined(__APPLE__)
p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
p = malloc(size);
#endif
} else {
size_t alignment = (alignMask = = ~(size_t(0)))
? _swift_MinAllocationAlignment
: alignMask + 1;
p = AlignedAlloc(size, alignment);
}
if (!p) swift::crash("Could not allocate memory.");
return p;
}
Copy the code
Inside the swift_slowAlloc function is to do some memory allocation operations, such as malloc. Therefore, the fourth reference type -> object request heap space process.
3. Swift class source structure
3.1. Differentiated calls between OC and Swift
The _swift_allocObject_ function is called with one argument, HeapMetadata named metadata. Here is the code process HeapMetadata follows:
// HeapMetadata is an alias of TargetHeapMetadata, and InProcess is generic.
using HeapMetadata = TargetHeapMetadata<InProcess>;
Copy the code
template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
using HeaderType = TargetHeapMetadataHeader<Runtime>;
TargetHeapMetadata(a)= default;
constexpr TargetHeapMetadata(MetadataKind kind)
: TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
: TargetMetadata<Runtime>(isa) {}
#endif
};
Copy the code
There is compatibility for OC and Swift here. The TargetHeapMetadata function is called with an ISA pointer if it is an OC class, or a MetadataKind type if it is not. MetadataKind is a uint32_t type.
enum class MetadataKind : uint32_t {
#define METADATAKIND(name, value) name = value,
#define ABSTRACTMETADATAKIND(name, start, end) \
name##_Start = start, name##_End = end,
#include "MetadataKind.def"
/// The largest possible non-isa-pointer metadata kind value.
///
/// This is included in the enumeration to prevent against attempts to
/// exhaustively match metadata kinds. Future Swift runtimes or compilers
/// may introduce new metadata kinds, so for forward compatibility, the
/// runtime must tolerate metadata with unknown kinds.
/// This specific value is not mapped to a valid metadata kind at this time,
/// however.
LastEnumerated = 0x7FF};Copy the code
So the MetadataKind category is as follows:
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 0x7FF
Copy the code
3.2. Swift class underlying source code structure
Next we find an inheritance of TargetHeapMetadata, TargetMetadata (structs are allowed to inherit in C++). The getTypeContextDescriptor function is found in the TargetMetadata structure as follows:
ConstTargetMetadataPointer<Runtime.TargetTypeContextDescriptor>
getTypeContextDescriptor() const {
switch (getKind()) {
case MetadataKind: :Class: {
const auto cls = static_cast<const TargetClassMetadata<Runtime> * >(this);
if (!cls->isTypeMetadata())
return nullptr;
if (cls->isArtificialSubclass())
return nullptr;
return cls->getDescription();
}
case MetadataKind: :Struct:
case MetadataKind: :Enum:
case MetadataKind: :Optional:
return static_cast<const TargetValueMetadata<Runtime> * >(this)
->Description;
case MetadataKind: :ForeignClass:
return static_cast<const TargetForeignClassMetadata<Runtime> * >(this)
->Description;
default:
returnnullptr; }}Copy the code
When kind is a Class, we get a pointer named TargetClassMetadata. If kind is a Class, we get a pointer named TargetClassMetadata.
Finally, we see something familiar, we look at its successor TargetAnyClassMetadata structure, you can see superclass, ISA, etc.
3.3. Swift class underlying source code structure
Through the above analysis, we can conclude that the metadata data structure in Swift class is roughly as follows:
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
_swift_allocObject_ () returns the pointer type of the HeapObject.
struct HeapObject {
/// This is always a valid pointer to a metadata object.
HeapMetadata const *__ptrauth_objc_isa_pointer metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
#ifndef __swift__
HeapObject(a)= default;
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized) {}// Initialize a HeapObject header for an immortal object
constexpr HeapObject(HeapMetadata const *newMetadata,
InlineRefCounts::Immortal_t immortal)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Immortal)
{ }
#ifndef NDEBUG
void dump() const SWIFT_USED;
#endif
#endif // __swift__
};
Copy the code
After knowing the source structure of HeapObject, we also fake in fake gas imitation source code, define a HeapObject, the following refcounted1 and refcounted2 can be ignored first, regardless of the main look at metadata.
struct HeapObject {
var metadata: UnsafeRawPointer
var refcounted1: UInt32
var refcounted2: UInt32
}
Copy the code
Next, we turn the SHPerson class into a HeapObject structure and print it through LLDB to see its memory structure.
class SHPerson {
var age = 18
var name = "Coder_ zhang SAN"
}
let p = SHPerson(a)// Convert SHPerson to a HeapObject pointer
let p_raw_ptr = Unmanaged.passUnretained(p as AnyObject).toOpaque()
let p_ptr = p_raw_ptr.bindMemory(to: HeapObject.self, capacity: 1)
// Convert the p_ptr pointer to the HeapObject pointer type and print out the HeapObject memory structure
print(p_ptr.pointee)
Copy the code
Print result:HeapObject(metadata: 0x00000001000081a0, refcounted1: 3, refcounted2: 0)
(lldb) x/8g 0x00000001000081a0
0x1000081a0: 0x0000000100008168 0x00007fff806208f8
0x1000081b0: 0x00007fff20208aa0 0x0000803000000000
0x1000081c0: 0x00000001085040f2 0x0000000000000002
0x1000081d0: 0x0000000700000028 0x00000010000000a8
(lldb) x/8g 0x0000000100008168
0x100008168: 0x00007fff80620920 0x00007fff80620920
0x100008178: 0x00007fff20208aa0 0x0000a03100000000
0x100008188: 0x0000000108504090 0x00000001000032b0
0x100008198: 0x00007fff8152f3e0 0x0000000100008168
(lldb)
Copy the code
Through printing, we know that the essence of Swift class is the structure pointer of HeapObject, and we print out its memory layout in the form of X /8g.
Next, I need to print the memory structure of HeapObject metadata to try:
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
}
struct HeapObject {
var metadata: UnsafeRawPointer
var refcounted1: UInt32
var refcounted2: UInt32
}
class SHPerson {
var age = 18
var name = "Coder_ zhang SAN"
}
let p = SHPerson(a)let p_raw_ptr = Unmanaged.passUnretained(p as AnyObject).toOpaque()
let p_ptr = p_raw_ptr.bindMemory(to: HeapObject.self, capacity: 1)
// We bind the metadata in HeapObject to the metadata type and convert it to the pointer type of metadata. MemoryLayout can be used to measure the size of the data type.
let metadata = p_ptr.pointee.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee
print(metadata)
Copy the code
Print result:Metadata(kind: 4295000432,
superClass: _TtCs12_SwiftObject,
cacheData: (140733732391584.140943646785536),
classFlags: 2,
instanceAddressPoint: 0,
instanceSize: 40,
instanceAlignmentMask: 7,
reserved: 0,
classSize: 168,
classAddressPoint: 16,
typeDescriptor: 0x0000000100003c6c,
iVarDestroyer: 0x0000000000000000)
(lldb)
Copy the code
We successfully print the values of member variables such as KIND, superClass, and cacheData.