First understanding of classes and structures
1.1 class and struct
In Swift, classes are modified with class and struct with struct, as shown below:
struct SSLTeacher {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
class SSLPerson {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
deinit {
}
}
Copy the code
1.2 Overview of similarities and differences
The main similarities between a structure and a class are:
- Defines attributes that store values
- Define methods
- Define subscripts to provide access to their values using subscript syntax
- Define initializers
- Use Extension to extend functionality
- Follow a protocol to provide a function
The main differences are:
- Classes have inherited properties that structs do not
- Type conversions enable you to examine and interpret the type of a class instance at run time classes have destructors to release their allocated resources
- Reference counting allows multiple references to a class instance
1.3 Classes are reference types
The first thing we need to distinguish between classes and structures is:
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 stored.
Let’s look at the memory structure of the current variable with instructions
P: type of return value and reference name of command result x/8g: read value in memory (8g: output in 8-byte format)Copy the code
Take a look at the following example:
- You can see that the addresses of t and T1 are the same, indicating that the class is a reference type.
It can be expressed as follows:
1.4 Structs are value types
Swift has reference types and value types, the most typical of which is Struct. The definition of structure is also very simple. In contrast, variables of class types store addresses, so value types store specific instances (or specific values).
Consider the following example:
- As you can see, the structure stores values directly.
1.5 Storage Area Differences
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.
For those of you who are not familiar with memory areas, check out the five memory areas.
1.5.1 Structure memory allocation
Let’s use the following example for analysis
struct SSLTeacher {
var age = 18
var name = "ssl"
}
func test() {
var t = SSLTeacher()
print("end")
}
test()
Copy the code
Next use the command
frame varibale -L xxx
Copy the code
- T is stored on the stack, age and name are stored on the stack
- The first address of t is directly the stored age variable, and age and name are also contiguous
- When you perform
var t = SSLTeacher()
This code will open up the required memory space on the stack whentest()
When the function completes, the memory space is reclaimed.
1.5.2 Class memory allocation
Again, use an example to analyze
- On the stack, eight bytes of memory are allocated to store the address of SSLTeacher
- On the heap, the appropriate memory area is found to open up memory and store values
- At the end of the function, the pointer on the stack is destroyed, and the sample variable on the heap is found and reclaimed.
1.5.3 Time allocation of structures and classes
As you can see above, the memory allocation of the class is a bit tedious, and there is reference counting and so on.
We can also visually test the time allocation of current structures and classes by using StructVsClassPerformance on Github.
After testing, we can find that the structure is significantly faster than the class.
1.5.4 Optimization cases
struct Attachment {
let fileURL: URL
let uuid: String
let mineType: String
...
}
Copy the code
Attachment is a value type, but uUID and mineType are reference types, which will affect performance. We will optimize them and modify them with value types
struct Attachment {
let fileURL: URL
let uuid: UUID
let mineType: MimeType
...
}
enum MimeType {
case jpeg = "image/jpeg"
...
}
Copy the code
Class initializer
Instances of classes and structures in Swift must be created with an appropriate initial value for all storage properties.
2.1 Structure initializer
Class compilers do not automatically provide member initializers by default, but they do provide default initializers for structures (if we don’t specify initializers ourselves)!
struct SSLTeacher {
var age: Int
var name: String
}
Copy the code
2.2 Class initializer
The class must provide a specified initializer, and we can also provide a convenient initializer for the current class (note: the convenient initializer must call another initializer from the same class).
class SSLPerson {
var age: Int
var name: String
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
convenience init(_ age: Int) {
self.init(18, "ssl")
self.age = age
}
}
Copy the code
- 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.
When we derive a subclass of SSLTeacher, look at its specified initializer written
class SSLTeacher: SSLPerson {
var subjectName: String
init(_ subjectName: String) {
self.subjectName = subjectName
super.init(18, "ss")
self.age = 17
}
}
Copy the code
- The specified initializer must ensure that all properties introduced by its class are initialized before delegating up to the parent initializer, in this case subjectName
- The specified initializer must delegate to the parent initializer before it can set new values for inherited properties. If this is not done, the new value assigned by the specified initializer will be overridden by the initializer in the parent class, in this case age.
2.3 Failable initializer
Failable initializer: This is easy to understand, meaning that the initialization failed due to 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 SSLPerson { var age: Int var name: String init? (_ age: Int, _ name: String) { if age < 18 {return nil} self.age = age self.name = name } convenience init?(_ age: Int) { self.init(18, "ssl") } }Copy the code
- If the age is less than 18 and is not considered a legal adult, the creation fails.
2.4 Necessary initializers
Required initializers: Add the required modifier before the class initializer to indicate that all subclasses of the class must implement the initializer
- If a subclass does not implement the required initializer, an error is reported.
Class life cycle
3.1 Swift compilation
Both OC and Swift backends of iOS development are compiled using 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.
Take a closer look at the Swift compilation process:
If you are interested in compiling the command, you can play it:
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
3.2 SIL file analysis
Write the following code in main.swift:
class SSLPerson {
var age: Int = 18
var name: String = "ssl"
}
var t = SSLPerson()
Copy the code
Run the following command
swiftc -emit-sil main.swift > ./main.sil && open main.sil
Copy the code
Use the command above to compile main.swift into the main.sil file, the file code is familiar, there are some related parsing below
class SSLPerson { @_hasStorage @_hasInitialValue var age: Int { get set } @_hasStorage @_hasInitialValue var name: String { get set } @objc deinit init() } // main sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 { bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>): alloc_global @$s4main1tAA9SSLPersonCvp // id: %2 %3 = global_addr @$s4main1tAA9SSLPersonCvp : $*SSLPerson // user: %7 %4 = metatype $@thick SSLPerson.Type // user: %6 // function_ref SSLPerson.__allocating_init() %5 = function_ref @$s4main9SSLPersonCACycfC : $@convention(method) (@thick SSLPerson.Type) -> @owned SSLPerson // user: %6 %6 = apply %5(%4) : $@convention(method) (@thick SSLPerson.Type) -> @owned SSLPerson // user: %7 store %6 to %3 : $*SSLPerson // id: %7 %8 = integer_literal $Builtin.Int32, 0 // user: %9 %9 = struct $Int32 (%8 : $Builtin.Int32) // user: %10 return %9 : $Int32 // id: %10 } // end sil function 'main' // SSLPerson.__allocating_init() sil hidden [exact_self_class] @$s4main9SSLPersonCACycfC : $@convention(method) (@thick SSLPerson.Type) -> @owned SSLPerson { // %0 "$metatype" bb0(%0 : $@thick SSLPerson.Type): %1 = alloc_ref $SSLPerson // user: // function_ref sslPerson.init () %2 = function_ref @$s4main9SSLPersonCACycfc: $@convention(method) (@owned SSLPerson) -> @owned SSLPerson // user: %3 %3 = apply %2(%1) : $@convention(method) (@owned SSLPerson) -> @owned SSLPerson // user: %4 return %3 : $SSLPerson // id: %4 } // end sil function '$s4main9SSLPersonCACycfC'Copy the code
- @main: entry function
- %0: register, virtual
- Sil syntax rules: github.com/apple/swift…
- This code basically parses the SSLPerson class, allocates memory in the heap, and calls key functions
__allocating_init()
, will be further analyzed through Swift source code.
3.3 Breakpoint assembly analysis
Break the code below to see the assembly
class SSLPerson {
var age: Int = 18
var name: String = "ssl"
}
var t = SSLPerson()
Copy the code
class SSLPerson : NSObject {
var age: Int = 18
var name: String = "ssl"
}
var t = SSLPerson()
Copy the code
- Find the key function
swift_allocObject
, the following source code analysis.
3.4 Swift source code analysis
Swift source code: github.com/apple/swift
Search the heapObject. CPP file to find the swift_allocObject function:
HeapObject *swift::swift_allocObject(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
CALL_IMPL(swift_allocObject, (metadata, requiredSize, requiredAlignmentMask));
}
Copy the code
Swift_allocObject calls the _swift_allocObject_ function:
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)); . }Copy the code
Swift_slowAlloc = swift_slowAlloc = swift_slowAlloc
void *swift::swift_slowAlloc(size_t size, size_t alignMask) { void *p; #if defined(__APPLE__) && SWIFT_STDLIB_HAS_DARWIN_LIBMALLOC p = malloc_zone_malloc(DEFAULT_ZONE(), size); #else p = malloc(size); . }Copy the code
- You can see that the malloc function is also called in Swift.
From this we can get the memory allocation of the Swift object:
__allocating_init
->swift_allocObject
->_swift_allocObject_
->swift_slowAlloc
->Malloc
- The memory structure of a Swift object
HeapObject
(OC objc_object), which has two properties: Metadata and RefCount, occupied by default16
Size in bytes.struct HeapObject { HeapMetadata const * metadata; InlineRefCounts refCounts; } Copy the code
Objc_object has only one ISA, and HeapObject lacks two attributes. Next, let’s explore Metadata
4. Exploring the structure of classes
4.1 Metadata source analysis
Metadata is of type HeapMetadata.
struct InProcess;
template <typename Target> struct TargetHeapMetadata;
using HeapMetadata = TargetHeapMetadata<InProcess>;
Copy the code
This is an aliases like definition, so look at 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
- TargetHeapMetadata inherits TargetMetadata
- In the initialization method, passed in when it is a pure Swift class
MetadataKind
If it interacts with OC, it passes oneisa
4.2 MetadataKind source code analysis
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"
LastEnumerated = 0x7FF,
};
Copy the code
And the type association table in Swift is as follows:
4.3 class structure source code analysis
Look at TargetHeapMetadata’s parent TargetMetadata:
struct TargetMetadata {
using StoredPointer = typename Runtime::StoredPointer;
private:
StoredPointer Kind;
}
Copy the code
- You can see that TargetMetadata has a Kind member variable, moving on
We find ConstTargetMetadataPointer function:
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:
return nullptr;
}
}
Copy the code
- You can see that when kidn is Class, this is forcibly cast as TargetClassMetadata.
Check the TargetClassMetadata:
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> { ... } struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> { TargetSignedPointer<Runtime, const TargetClassMetadata<Runtime> *superclass; TargetPointer<Runtime, void> CacheData[2]; StoredSize Data; . }Copy the code
In TargetAnyClassMetadata we can find superclass, Data, CacheData and other member variables. After the above series of analysis, we can get the Data structure of swift class 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