One, variation method
1.1 Variation method
A value type attribute cannot be modified by its own instance method
Note: moveBy is an instance method, not a builder.
Value types that want to be modified by their own instance methods must be preceded by onemutating
modified
So what does mutating look like underneath? Let’s add a general method and look through the SIL file:
// Point.move(_:) sil hidden @$s4main5PointV4moveyySdF : $@convention(method) (Double, Point) -> () { // %0 "deltaX" // users: %4, %2 // %1 "self" // user: %3 bb0(%0 : $Double, %1 : $Point): debug_value %0 : $Double, let, name "deltaX", argno 1 // id: %2 debug_value %1 : $Point, let, name "self", argno 2 // id: %3 debug_value %0 : $Double, let, name "u" // id: %4 %5 = tuple () // user: %6 return %5 : $() // id: %6 } // end sil function '$s4main5PointV4moveyySdF' // Point.moveBy(_:_:) sil hidden @$s4main5PointV6moveByyySd_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 return %26: $() // id: %27 } // end sil function '$s4main5PointV6moveByyySd_SdtF'Copy the code
As we all know, the default method also passes the self argument, look at the code above
The mutating modifier adds the inout keyword to a Point
Move (_) method %1 arguments: $Point, let, name “self”, argno 2 meaning self is let constant
MoveBy (:) method %2 argument: $*Point, var, name “self”, argno 3 meaning self is the var variable value
That is, the self of the mutating method is passed as a pointer, so it can modify the value of the address. (The Ponit argument refers to self, so we can use self in the method)
S4main5PointV4moveyySdF and s4main5PointV6moveByyySd_SdtF are move(:) and moveBy(:_:) functions respectively. Xcrun swift-demangle
The nature of mutant methods: For mutant methods, self passed in is marked as an inout parameter. Whatever happens inside the mutating method affects everything about the external dependency type.
1.2 inout
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 the formal parameter as inout.
struct Point{
var x = 0.0, y = 0.0
func move(_ deltaX:Double._ deltaY: inout Double){
let u = deltaX
deltaY = 50}}Copy the code
Look at the SIL:
// Point.move(_:_:) sil hidden @$s4main5PointV4moveyySd_SdztF : $@convention(method) (Double, @inout Double, Point) -> () { // %0 "deltaX" // users: %6, %3 // %1 "deltaY" // users: %9, %4 // %2 "self" // user: %5 bb0(%0 : $Double, %1 : $*Double, %2 : $Point): debug_value %0 : $Double, let, name "deltaX", argno 1 // id: %3 debug_value_addr %1 : $*Double, var, name "deltaY", argno 2 // id: %4 debug_value %2 : $Point, let, name "self", argno 3 // id: %5 debug_value %0 : $Double, let, name "u" // id: // end sil function '$s4main5PointV4moveyySd_SdztF'Copy the code
Second, method scheduling
A little prep: register instructions
2.1 Class method scheduling
Here’s the test code:
class CCTeacher{
func teach(a){
print("teach")}func teach1(a){
print("teach1")}func teach2(a){
print("teach2")}}class ViewController: UIViewController{
override func viewDidLoad(a) {
let t = CCTeacher()
t.teach()
t.teach1()
t.teach2()
}
}
Copy the code
Here are two ways to explore class method scheduling
1, LLDB breakpoint, observe register instruction
2. Observe the SIL code
2.1.1 LLDB breakpoint, observe register instruction
Run the real machine with a breakpoint
Observe the breakpoint: just three BLR method jumps, that is, method calls
The instance object of the function is placed in the X0 register
mov x20, x0
Copy the value of x0 into x20, or copy the instance object into x20
ldr x8, [x20]
Take the value in x20, put it in x8, so now x8 is the instance object, take the first 8 bytes of the instance object, because the register is 64 bits, hold 8 bytes, and what is the first 8 bytes in x20? Yes, the metadata
ldr x8, [x8, #0x50]
Add the values of x8 and #0x50 (offset) and place them in x8
blr x8
Finally, call x8
Metadata -> determine the address of the function (Metadata + offset) -> execute the function
2.1.2 Function table V_table
Take a closer look at the offset added before the call:
There are 8 bytes between #0x50 #0x58 #0x60! Then these 8 bytes are the size of the function pointer, and they are contiguous memory space in memory, which can lead to swift’s first function scheduling mode: scheduling based on function table V_table
Compile a SIL to see:
sil_vtable CCTeacher { #CCTeacher.teach: (CCTeacher) -> () -> () : @$s14ViewController9CCTeacherC5teachyyF // CCTeacher.teach() #CCTeacher.teach1: (CCTeacher) -> () -> () : @$s14ViewController9CCTeacherC6teach1yyF // CCTeacher.teach1() #CCTeacher.teach2: (CCTeacher) -> () -> () : @$s14ViewController9CCTeacherC6teach2yyF // CCTeacher.teach2() #CCTeacher.init! allocator: (CCTeacher.Type) -> () -> CCTeacher : @$s14ViewController9CCTeacherCACycfC // CCTeacher.__allocating_init() #CCTeacher.deinit! deallocator: @$s14ViewController9CCTeacherCfD // CCTeacher.__deallocating_deinit }Copy the code
In the SIL file, the sil_vtable is the function table, which lists the functions of the CCTeacher class
2.1.3 Querying VTABLE from source Code
The Swift class contains a metadata containing a member that is:
var typeDescriptor: UnsafeMutableRawPointer
Copy the code
Classes, structs, and enumerations all have this member variable that holds a description of themselves
Find the definition of typeDescriptor in the source code, and the search process is as follows:
- Find HeapMetadata in the heapObject.h file
using HeapMetadata = TargetHeapMetadata<InProcess>;
Copy the code
HeapMetadata is an alias of TargetHeapMetadata
- Enter the
TargetHeapMetadata
The structure of the body
Find:
ConstTargetMetadataPointer<Runtime, TargetClassDescriptor> Description;
Copy the code
At this point, Description is a class that’s TargetClassDescriptor, and inherits a bunch of stuff
After the members are sorted, you can get something like this:
struct TargetClassDescriptor{
var flags: UInt32
var parent: UInt32
var name: Int32
var accessFunctionPointer: Int32
var fieldDescriptor: Int32
var superClassType: Int32
var metadataNegativeSizeInWords: UInt32
var metadataPositiveSizeInWords: UInt32
var numImmediateMembers: UInt32
var numFields: UInt32
var fieldOffsetVectorOffset: UInt32
var Offset: UInt32
var size: UInt32
//V-Table
}
Copy the code
And then you scroll down and you see that TargetClassDescriptor has a single name: ClassDescriptor
using ClassDescriptor = TargetClassDescriptor<InProcess>;
Copy the code
Global search: ClassDescriptor, you can find the genmeta. CPP file, this is where the metadata is generated
Enters GenMeta. CPP files, positioning to ClassContextDescriptorBuilder, that’s it, to create the metadata and Descriptor
Scroll down to the Layout () method
void layout(a) {
super::layout(a);addVTable(a);addOverrideTable(a);addObjCResilientClassStubInfo(a); }Copy the code
It calls super:: Layout () first, so let’s look at its parent class:
void layout(a) {
asImpl().computeIdentity(a); super::layout(a);asImpl().addName(a);asImpl().addAccessFunction(a);asImpl().addReflectionFieldDescriptor(a);asImpl().addLayoutInfo(a);asImpl().addGenericSignature(a);asImpl().maybeAddResilientSuperclass(a);asImpl().maybeAddMetadataInitialization(a); }Copy the code
It also calls void Layout () :
void layout(a) {
asImpl().addFlags(a);asImpl().addParent(a); }Copy the code
Well, if you string it together, does it ring a bell?
It’s basically all right, so this layout, this layout, is just building TargetClassDescriptor
Look at the addVTable() function:
void addVTable(a) {
if (VTableEntries.empty())
return;
if (IGM.hasResilientMetadata(getType(), ResilienceExpansion::Minimal))
IGM.emitMethodLookupFunction(getType());
auto offset = MetadataLayout->hasResilientSuperclass()? MetadataLayout->getRelativeVTableOffset()
: MetadataLayout->getStaticVTableOffset(a); B.addInt32(offset / IGM.getPointerSize());
B.addInt32(VTableEntries.size());
for (auto fn : VTableEntries)
emitMethodDescriptor(fn);
}
Copy the code
B. adddint32, calculated the offset and added it to B. B added the size of vtable and traversed the VTableEntries array. Call emitMethodDescriptor(SILDeclRef fn) and add the function pointer fn, and the B is the current TargetClassDescriptor, in other words, add something to B, You just add something to the TargetClassDescriptor
2.1.4 Analyzing class methods in Mach-O
Open MachOView to view Mach-O and look at the base address of virtual memory: 0x100000000
See: Section64(__Text,__swift5_types) section, where you store structure, enumerations, class descriptors, the first four bytes store class descriptors, So 90 FB FF FF is the address information of the CCTeacher Descriptor, so how do you verify that it stores the address information?
Add the preceding four bytes, which are offsets in The Mach-O, to get the memory address of the Descriptor in the Mach-O file
Since this address is in small-endian mode, read from right to left: FF FF FB 90
FFFFFB90
+ 0000BBCC
= 0x10000B75C
Then take the result of this calculation and subtract the virtual memory base address to get the memory address in the Mach – O file Descriptor
0x10000B75C
– 0x100000000
= B75C
Const (); const ();
In other words, starting at 50, find a place where you can store the contents of the Descriptor, which means that 50 is the first address of the structure of the TargetClassDescriptor, and the contents after that should correspond to the contents of the structure, so in that case, I count 12 4-bytes against TargetClassDescriptor and go straight to vtable (because vtable is number 13) :
2.1.5 Verify the Mach-O analysis through the program
The above analysis shows that teach() corresponds to an offset address of 000B790 in Mach-o
Command image list
this0x0000000100a8c000
isThe base address at which the program is runThat isASLRIs the starting address of the project after it is loaded into memory.
Add the offset 000B790 of teach() in Mach -o to the base where the program is run
0x0000000100a8c000
+ 000B790
= 0x100A97790
That is to say,0x100A97790
Point to this structure:
Look at the source code again. LookTargetMethodDescriptor
This structure, it represents the structure of the Swift method Flags
Identify what type of method this is
class MethodDescriptorFlags {
public:
typedef uint32_t int_type;
enum class Kind {
Method,
Init,
Getter,
Setter,
ModifyCoroutine,
ReadCoroutine,
};
Copy the code
The Impl is a relative pointer, an Offset
So that means that 0x100A97790 that we just figured out points to this structure of TargetMethodDescriptor, and since the first member of this structure is Flags, we’re going to offset it, Flags is an enumeration of uint32, so it’s 4 bytes, we’re going to offset it by 4 bytes, Impl = Impl; Impl = Impl; Impl = Impl;
Method address = 0x100A97790 + FLAGS (4 bytes) + IMPL
What is the IMPL content?
The first four bytes are flags. The next four bytes are the IMPL. The IMPL is FF, FF, C2, 5C
0x100A97790
+ 4
+ FFFFC25C
= 0x200A939F0
Finally, subtract the base address of mach-O virtual memory 0x100000000
0x200A939F0
– 0x100000000
= 0x100A939F0
0x100A939F0 is the address of teach()
Assembly interrupt point verification:
2.2 Struct method scheduling
Replace the above class with a struct and run a breakpoint
struct CCTeacher{
func teach(a){
print("teach")}func teach1(a){
print("teach1")}func teach2(a){
print("teach2")}}class ViewController: UIViewController{
override func viewDidLoad(a) {
let t = CCTeacher()
t.teach()
t.teach1()
t.teach2()
}
}
Copy the code
Good guy, direct BL function address, that is to call the function directly.
This means that, after compiling, the address of the function is determined. Because the structure has no inheritance relationship, it does not need to create a separate memory space to record the location of each function, so the function address is determined after the compilation link, directly optimized for static calls
Iii. Function distribution mode
3.1 the final
Two points to note:
- be
final
Modified class that cannot be inherited - be
final
Modifier methods, subscripts, and attributes are not allowed to be overridden for byfinal
Decorated methods are issued statically directly and will not be added tovtable
And rightobjc
Not visible at runtime.
3.2 the dynamic
- Functions can be added
dynamic
Keyword, assigned to functions that are not objC classes and value typesdynamic
, but the distribution is stillFunction table vtable
Distributing. - But with
@_dynamicReplacement(for:)
Together with
class CCTeacher {
dynamic func teach(a){
print("teach")}}extension CCTeacher {
Teach3 () teach3()
@_dynamicReplacement(for:teach())
func teach3(a){
print("teach3")}}let t = CCTeacher()
t.teach()
// Print the result: Teach3
Copy the code
3.3 @objc / @objc + dynamic
@objc
Keywords can beSwift function
Exposed to theObjc runtime
, thus using the Runtime method, such as method exchange, but the function table vtable is still distributed, that is to say, oc class still cannot use the swift function.
class CCTeacher {
@objc dynamic func teach(a){
print("teach")}}Copy the code
- To make the SWIFT function available to the OC class, the SWIFT class inherits from
NSObject
@objc + dynamic
The mode of message dispatch, also known as messaging in oc.