One, variation method
1 mutating
The keyword
In the previous article, you learned that both classes and structs in Swift can define methods. One difference, however, is that by default, a value type attribute cannot be modified by its own instance method.
Self cannot be modified because x and y currently belong to self
Mutating before the func keyword allows you to modify the behavior
- Think 🤔 : do not add
mutating
Access and Addmutating
What is the essential difference between visiting both ❓
1.1 Through GenerationSIL
File analysis
main.swift
File to add test code
Struct Point {var x = 0.0, y = 0.0 func test(){let TMP = self.x print(TMP)} mutating func moveBy(x deltaX: Double, y deltaY: Double){ x += deltaX y += deltaY } }Copy the code
- generate
SIL file command
:
swiftc -emit-sil main.swift >> main.sil
Copy the code
Obtain the main. Sil file, through the sil file comparative analysis as follows
test
function
// Point.test()
sil hidden @$s4main5PointV4testyyF : $@convention(method) (Point) -> () {
// %0 "self" // users: %2, %1
bb0(%0 : $Point):
debug_value %0 : $Point, let, name "self", argno 1 // id: %1
......
}
Copy the code
moveBy
function
// Point.moveBy(x:y:)
sil hidden @$s4main5PointV6moveBy1x1yySd_SdtF : $@convention(method)
(Double, Double, @inout Point) -> () {
// %0 "deltaX" // users: %10, %3
// %1 "deltaY" // users: %20, %4
// %2 "self" // users: %16, %6, %5
bb0(%0 : $Double, %1 : $Double, %2 : $*Point):
debug_value %0 : $Double, let, name "deltaX", argno 1 // id: %3
debug_value %1 : $Double, let, name "deltaY", argno 2 // id: %4
debug_value_addr %2 : $*Point, var, name "self", argno 3 // id: %5
......
}
Copy the code
1.2 Comparative analysis
test
The default argument to the function isPoint
Receives an instance of the structure (self
, which is the value)- Assignment process:
let self = Poit
- Assignment process:
- The default argument to moveBy is
@inout
Point
, receives the address- Assignment process:
var self = &Poit
- Assignment process:
Note: Let declarations are immutable while var declarations are mutable
1.3 Example code analysis
var p = Point() // let self = Poit var x1 = p // var sefl = &Poit var x2 = withUnsafePointer(to: &p) {return $0} var x3 = p p.x = 30.0 print(x2.pointe.x) print(x3.x)Copy the code
Output result:
30.0
0.0
Copy the code
- Changes can be made based on the results
Point
Value:x2
Can be modified,x3
Do not modify the
The essence of mutating: For mutating methods, the passed self is marked as an inout parameter. Whatever happens inside the mutating method affects everything about the external dependency type.
2. inout
Input/output parameter
2.1 inout SILA document explaining
An @inout parameter is indirect. The address must be of An initialized object.
Example code analysis
var age = 10
func modifyage(_ age: inout Int) {
age += 1
}
modifyage(&age)
print(age)
Copy the code
Output result:
11
Copy the code
inout
Address passing
If we want a function to be able to change the value of a formal parameter, and we want those changes to persist after the function ends, we need to define formal parameters as input and output formal parameters. Adding an inout keyword at the beginning of the formal parameter definition defines an input/output formal parameter
Second, method scheduling
Reviewing OC language, the essence of calling method is message passing, and the underlying method is scheduling through objC_mgSend message mechanism
Addendum: Common assembly instructions
- Mov: Copy the value of a register to another register (used only between registers or between registers or constants, not memory addresses).
Mov x1, x0 copies the value of register x0 into register x1Copy the code
- Add: Add the value of one register to the value of another register and save the result in the other register
Add the values of registers X1 and x2 and save them to register x0Copy the code
- Sub: Subtracts the value of one register from the value of another register and saves the result in the other register
Sub x0, x1, x2 Subtracts the values of registers x1 and x2 and saves them in register x0Copy the code
- And: bitwise sums the value of one register with the value of another register and saves the result to another register
And x0, x0, #0x1 save the value of register x0 and constant 1 to register x0Copy the code
- ORR: Bitwise or save the value of one register with the value of another register
ORR x0, x0, #0x1 bits or the value of register x0 and constant 1 and saves the result in register x0Copy the code
- STR: writes the value in the register to memory
str x0, [x0, x8] ; Save the value in register X0 to stack memory at [x0 + x8]Copy the code
- LDR: Reads a value from memory into a register,
LDR x0, [x1, x2] adds the value of register X1 and register x2 as the address, and places the value of the address in register x0Copy the code
- CBZ: compares with 0 and jumps if the result is zero (can only jump to subsequent instructions)
- CBNZ: Compares with non-zero, and if the result is non-zero then jumps (only to subsequent instructions)
- CMP: Comparison instruction
- BLR: (branch) Jumps to an address (no return)
- Bl: Jumps to an address (returns)
- Ret: The subroutine (function call) returns the instruction, the return address has been saved in register LR (X30) by default
2.1 Assembly analysis
Create a Swift project. Define LGTeacher class, define teach function. Type the sign breakpoint as follows:
- Run the view assembly breakpoints below
- Assembly analysis, starting with initialization
- An instruction that calls the initialization of a function
bl
, has a return value. Returns theLGTeacher
Instance object, the return value of the function is placed inx0
In the register - The first 8 bytes of x0 are: medata
- X8 + (0x50) offset function call address
- An instruction that calls the initialization of a function
- It can be concluded that there is an offset operation before a function call:
ldr x8, [x8, #0x50]
,ldr x8, [x8, #0x58]
,ldr x8, [x8, #0x60]
Find the Metadata schedule based on the function table, determine the function address (Metadata + offset), and execute the function
2.2 SIL
validation
Command to generate the viewController.sil file
Swiftc-emit -silgen - onone-target x86_64-apple-ios13.3-simulator - SDK $(xcrun -- show-sdK-path -- SDK iphonesimulator) ViewController.swift > ./ViewController.silCopy the code
Slide to the bottom of the viewController.sil file
sil_vtable LGTeacher { #LGTeacher.teach: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC5teachyyF // LGTeacher.teach() #LGTeacher.teach1: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC6teach1yyF // LGTeacher.teach1() #LGTeacher.teach2: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC6teach2yyF // LGTeacher.teach2() #LGTeacher.init! allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s14ViewController9LGTeacherCACycfC // LGTeacher.__allocating_init() #LGTeacher.deinit! deallocator: @$s14ViewController9LGTeacherCfD // LGTeacher.__deallocating_deinit } sil_vtable ViewController { #ViewController.deinit! deallocator: @$s14ViewControllerAACfD // ViewController.__deallocating_deinit }Copy the code
Vtable is called function table, sil_vtable contains all the functions in the class.
2.3 swift
Source code analysisV-table
The Metdata data structure was discussed in the previous chapter, so where is v-table stored? Let’s start with a review of current data structures
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
There’s one thing we need to focus on here, whether it’s Class, Struct, Enum, they all have their own Descriptor, which is a detailed description of the Class
- Open Swift source code analysis
- Open the
Metadata.h
file - find
TargetClassMetadata
The structure of the body
- Find inside
Description
variable
Click on the TargetClassDescriptor to enter the structure, search for the TargetClassDescriptor, and find the alias (ClassDescriptor) as follows:
using ClassDescriptor = TargetClassDescriptor<InProcess>;
Copy the code
Global search ClassDescriptor alias, enter GenMeta. CPP files, locating the ClassContextDescriptorBuilder class, this class is to describe, to create the metadata and Descriptor.
Go to the Layout method
void layout() { assert(! getType()->isForeignReferenceType()); super::layout(); addVTable(); addOverrideTable(); addObjCResilientClassStubInfo(); maybeAddCanonicalMetadataPrespecializations(); }Copy the code
The parent class is implemented in the function. Click super:: Layout () to enter the parent class implementation:
void layout() {
asImpl().computeIdentity();
super::layout();
asImpl().addName();
asImpl().addAccessFunction();
asImpl().addReflectionFieldDescriptor();
asImpl().addLayoutInfo();
asImpl().addGenericSignature();
asImpl().maybeAddResilientSuperclass();
asImpl().maybeAddMetadataInitialization();
}
Copy the code
Some of the TargetClassDescriptor class member variables have been derived here. Return subclass to addVTable function as follows:
void addVTable() {
LLVM_DEBUG(
llvm::dbgs() << "VTable entries for " << getType()->getName() << ":\n";
for (auto entry : VTableEntries) {
llvm::dbgs() << " ";
entry.print(llvm::dbgs());
llvm::dbgs() << '\n';
}
);
// Only emit a method lookup function if the class is resilient
// and has a non-empty vtable, as well as no elided methods.
if (IGM.hasResilientMetadata(getType(), ResilienceExpansion::Minimal)
&& (HasNonoverriddenMethods || !VTableEntries.empty()))
IGM.emitMethodLookupFunction(getType());
if (VTableEntries.empty())
return;
auto offset = MetadataLayout->hasResilientSuperclass()
? MetadataLayout->getRelativeVTableOffset()
: MetadataLayout->getStaticVTableOffset();
B.addInt32(offset / IGM.getPointerSize());
B.addInt32(VTableEntries.size());
for (auto fn : VTableEntries)
emitMethodDescriptor(fn);
}
Copy the code
AddVTable function analysis: offset calculates the offset, calls addInt32 to add the offset to B(Descriptor); Finally, the for loop adds a pointer to the function.
The resulting TargetClassDescriptor structure is as follows:
struct TargetClassDescriptor{
var flags: UInt32
var parent: UInt32
var name: Int32
var accessFunctionPointer: Int32
var fieldDescriptor: Int32
var superClassType: Int32
var metadataNegativeSizeInWords: UInt32
metadataPositiveSizeInWords: UInt32
var numImmediateMembers: UInt32
var numFields: UInt32
var fieldOffsetVectorOffset: UInt32
var Offset: UInt32
var size: UInt32
//V-Table
}
Copy the code
2.4 what isMahco
❓
Mach-O is short for Mach Object file format, which is a latticework for MAC and iOS executables. It is similar to the Portable PE Format on Windows and the ELF Format on Linux. Common.o,.a,.dylib, Framework, dyld,.dsym.
Mahoc
File Format:
Against 2.4.1Header
(Header file)
The first is the file header, which indicates that the file is in Mach-O format, specifies the target schema, and some other information about the file attributes, which affects the subsequent structure of the file
2.4.2 Load commands
Load Commands is a table that contains many things. The content includes the location of the region, symbol table, dynamic symbol table, etc.
2.4.3 Data
data
The Data section is mainly responsible for code and Data logging. Mach-o organizes data in segments. A Segment can contain zero or more sections. Depending on which Load Command the Segment is mapped to, sections in a Segment can be interpreted as code, constants, or some other data type. When loaded in memory, memory mapping is done based on Segment.
2.2.4 MachOViewTool presentation projectMach-O
The file format
- Open the project Products directory
- Display package contents
- Drag the MachOView tool to open
Mach-O
The file format is as follows
2.5 classfunctionMach-O
File analysis
Execute class function source code
Class LGTeacher{// class LGTeacher? No func teach() {print("teach")} func teach1(){print("teach1")} func teach2(){print("teach2")}} class ViewController: UIViewController{ override func viewDidLoad() { let t = LGTeacher() t.teach() t.teach1() t.teach2() } }Copy the code
2.5.1 Search function inMach-O
Address in file
__swift5_types stores Class, Struct, and Enum descriptors
The first four bytes F4 FB FF FF are LGTeacher Descriptor information, F4 FB FF FF plus 0000BC58 in the pFile field to get Descriptor address information in the Mach-O file
The current ios is in small – end mode. Read data from right to left. Descriptor address information addition equation:
FFFFFBF4+0000BC58 = 0x10000B84C
Copy the code
The address information of the Descriptor is 0x10000B84C, 0x100000000 is the base address of the virtual memory in the Mach-O file
0x10000B84C-0x100000000 = 0xB84C, 0xB84C is the first address of LGTeacher, followed by the contents of the TargetClassDescriptor structure
In the TargetClassDescriptor, there are 12 fields before size, 13 fields after size. the address of teach, teach1, and teach2 in the V-table is as shown in the following figure:
The resulting teach function has an offset of B880 in the Mach-o file
2.5.2 Verifying the current functionMach-OFile address in the program running address
Teach () run address: B880 + ASLR
The breakpoint executes the function, executes the LLDB function image List to get ASLR(the base address of the program to run) 0x0000000104DA8000
The structural address of teach() function is 0x0000000104DA8000 + B880 = 0x104DB3880, as shown in the following figure:
Find the TargetMethodDescriptor structure in the source code, which is the in-memory structure of Swift’s method
Template <typename Runtime> struct TargetMethodDescriptor {/// Flags describing the method. // 4 bytes MethodDescriptorFlags Flags; /// The method implementation. // offset TargetRelativeDirectPointer<Runtime, void> Impl; // TODO: add method types or anything else needed for reflection. };Copy the code
Impl
Address:0x104DB3880
+0x4
+0xFFFFB9D4
=0x204DAF258
- 0x4: Flags
- 0xFFFFB9D4: Offset (
D4 B9 FF FF
) Small end mode from right to left
teach
The address of the function is0x204DAF258
–0x100000000
=0x104DAF258
- 0x100000000: Base address of virtual memory in mach-o file
Open the assembly debugging mode and read the address of teach function in the assembly, as shown in the figure below:
Finally -> 0x104DAF258 is the address of teach function, V-table is after Descriptor structure, and Swift class methods are stored in v-table function Table.
؏؏☝ᖗ乛◡乛ᖘ☝؏؏
2.5.3 whyMethodData
+ Offset
(offset operation)?
The function is vtableOffset when stored in vtable, so it must be offset when fetched
2.6 structFunction scheduling
Struct LGTeacher{// No func teach() {print("teach")} func teach1(){print("teach1")} func teach2(){print("teach2")}} class ViewController: UIViewController{ override func viewDidLoad() { let t = LGTeacher() t.teach() t.teach1() t.teach2() } }Copy the code
Assembly debugging results:Struct function call, the address of the current function has been determined after compilingStatic distributed
2.7 struct 的 extension
Function scheduling
Struct LGTeacher add extension
struct LGTeacher{
......
}
extension LGTeacher {
func teach3(){
print("teach2")
}
}
Copy the code
Assembly debugging results:
Teach3 is directly called by address, extension is also statically distributed in struct
2.8 class 的 extension
Function scheduling
Class LGTeacher add extension implementation class
class LGTeacher{
......
}
extension LGTeacher {
func teach3(){
print("teach2")
}
}
Copy the code
Assembly debugging results:
teach3
Not in theV-Table
Function table, but directly address calls, extension class also belongs toStatic distributed
Method scheduling method summary
type | scheduling | extension |
---|---|---|
Value types | Static distributed | Static distributed |
class | Function table distribution | Static distributed |
NSObject subclass | Function table distribution | Static distributed |
Iii. Distribution mode of influence function
3.1 finalThe keyword
- Final: Functions with the final keyword added cannot be overridden, are statically distributed, do not appear in the VTable, and are not visible to objC runtime.
Assembly debugging results:
SIL analysis (without teach function) :
Added the final keyword, removed from sil_vtable and optimized for direct calls
Properties, methods, and classes do not need to be overloaded during actual development, and final can be used
3.2 dynamicThe keyword
- Dynamic: The dynamic keyword can be added to all functions to give dynamics to functions of non-objC classes and value types, but the distribution mode is still function table distribution.
Class LGTeacher{// class LGTeacher? Dynamic func teach() {print("teach")}} extension LGTeacher {@_dynamicreplacement (for: teach) func teach3(){ print("teach3") } } class ViewController: UIViewController{ override func viewDidLoad() { let t = LGTeacher() t.teach() } }Copy the code
Output result:
teach3
Copy the code
3.3 @objcThe keyword
- This keyword exposes the Swift function to the Objc runtime, which is still distributed from the function table.
class LGTeacher : NSObject {
@objc func teach() {
print("teach")
}
}
class ViewController: UIViewController{
override func viewDidLoad() {
let t = LGTeacher()
t.teach()
}
}
Copy the code
See how it is exposed to Objc
View the apis exposed to Objc
3.4 @objc + dynamicThe keyword
- The way messages are distributed
Class LGTeacher{// class LGTeacher? @objc dynamic func teach() {print("teach")}} class ViewController: UIViewController{ override func viewDidLoad() { let t = LGTeacher() t.teach() } }Copy the code
Assembly debugging:
4. Function inlining
Function inlining is a compiler optimization technique that optimizes performance by calling a method directly using its content substitution.
4.1 OCProject compiler optimization
int sum(int a, int b) {
return a + b;
}
int main(int argc, char * argv[]) {
int a = sum(3, 5);
NSLog(@"%d", a);
}
Copy the code
Not optimized
Debug Tests in Default None mode
Assembly debugging:
First, store 3 and 5 in W0 and W1 respectively, and then calculate the sum
Optimize Debug by selecting Fastest, Samallest mode to test
Assembly debugging:
As a result, the calculation results are directly stored in W8 and output by calling NSLog function.
4.2 Swift
An inline function
Optimization Settings
- Will ensure that functions are sometimes inlined. This is the default behavior and we don’t need to do anything. The Swift compiler may automatically inline functions as optimizations.
- Always – Will ensure that functions are always inlined. This behavior is implemented by prefixing the function with @inline(__always)
@inline(__always) func test() {
print("test")
}
Copy the code
- Never – Will ensure that functions are never inlined. This can be done by prefixing the function with @inline(never)
@inline(never) func test() {
print("test")
}
Copy the code
- If the function is long and you want to avoid increasing the code segment size, use @inline(never).
4.3 PrivateModify functions
If the object is visible only in the declared file, you can decorate it with private or Fileprivate. The compiler checks the private or Fileprivate objects to make sure that there are no other inheritance relationships, and then automatically marks them as final, so that the objects get statically distributed features. (Fileprivate: only accessible in defined source files, private: Definition of access in the declaration)
class LGPerson{ private var sex: Bool private func unpdateSex(){ self.sex = ! self.sex } init(sex innerSex: Bool) { self.sex = innerSex } func test() { self.unpdateSex() } }Copy the code