preface
Previously, we explored the important variables in the class, and analyzed the cache method invocation process. So I’ve traced objc_msgSend, so let’s look at objc_msgSend
The preparatory work
- Objc4-818.2 – the source code
- Objective-C Runtime
Runtime
Runtime
Introduction to the
What’s the difference between Runtime, which is commonly called Runtime, and compile time, which is commonly called compile time
Compile time
What is a compiler when it is compiling? isThe compiler translates the source code into code that the machine can recognize
. The lexing is done at compile time, and the parsing is mainly to check if the code complies with Apple’s specifications. This checking process is often called static type checkingThe runtime
:The code runs and is loaded into memory
. Error checking at run time is not the same as error checking at compile time. It is not a simple code scan, but an operation and judgment in memory
Runtime
version
There are two versions of Runtime, a Legacy version (earlier version) and a Modern version (current version)
- Earlier versions of the corresponding programming interface:
Objective - 1.0 C
- Current version of the corresponding programming interface:
Objective - 2.0 C
, often seen in source codeOBJC2
- Earlier versions used for
Objective - 1.0 C
, 32 bitsMac OS X
The platform of - Current version used for
Objective - 2.0 C
, iPhone apps andMac OS X v10.5
64-bit programs on and after systems
Runtime
Call in three ways
Objective-C
Way,[penson sayHello]
Framework & Serivce
Way,isKindOfClass
Runtime API
Way,class_getInstanceSize
Nature of method
Method underlying implementation
There are two ways to explore the bottom of the approach. The first assembly, the second C++ code. The parameters of the assembler method need to read the register is not convenient, so the second method is used to generate the main.cpp file. First, customize the LWPerson class by adding an instance method to the class and calling it in the main function
int main(int argc, char * argv[]) {
@autoreleasepool {
LWPerson * perosn = [LWPerson alloc];
[perosn sayHello];
[perosn showTime:10];
}
return 0;
}
Copy the code
Clang generates a main. CPP file from main.m and queries the implementation of main
Source code analysis: All method calls are sent through objc_msgSend, so the essence of methods is message sending
Since methods are called through objc_msgSend, can I send messages directly through objc_msgSend
int main(int argc, char * argv[]) {
@autoreleasepool {
LWPerson * perosn = [LWPerson alloc];
[perosn sayHello];
//objc_msgSend(void /* id self, SEL op, ... */ )
objc_msgSend((id)perosn, sel_registerName("sayHello"));
}
return 0;
}
Copy the code
2021- 0626 - 18:14:22.659269+0800 objc_msgSend[5461:254082] sayHello
2021- 0626 - 18:14:22.659722+0800 objc_msgSend[5461:254082] sayHello
Copy the code
The result is the same with objc_msgSend and [perosn sayHello], which also validates that the essence of the method is message sending. Objc_msgSend is being used to send messages. The validation process requires two considerations
- The corresponding header file must be imported
#import <objc/message.h>
- Shut down
objc_msgSend
Check mechanism:target
–>Build Setting
– > searchobjc_msgSend
—Enable strict checking of obc_msgSend calls
Set toNO
Call the superclass method
Calling methods in this class is actually sent through objc_msgSend, so what does calling method message sending of the class look like? Custom LWAllPerson class. LWPerson inherits from LWAllPerson class. Customize helloWord in the LWAllPerson class, and subclass objects call the helloWord method
int main(int argc, char * argv[]) {
@autoreleasepool {
LWPerson * perosn = [LWPerson alloc];
[perosn helloWord];
}
return 0;
}
Copy the code
clang
themain.m
generatemian.cpp
File, querymain
Implementation of function
Clang generates lwPerson. CPP file from lwPerson. m to query the implementation of LWPerson function
A subclass object can use objc_msgSendSuper to call a method of the superclass. The essence of the method is to send a message, but through a different sending process. Now use objc_msgSendSuper to send a message to the superclass. The first argument to objc_msgSendSuper is void /* struct objc_super *super. Look for objc_super in the source code
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if! defined(__cplusplus) && ! __OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
Copy the code
The objc_super structure type has two parameters, ID Receiver and Class super_class
int main(int argc, char * argv[]) {
@autoreleasepool {
LWPerson * perosn = [LWPerson alloc];
//(void *)objc_msgSendSuper)((__rw_objc_super){(id)self,
//(id)class_getSuperclass(objc_getClass("LWPerson"))}, sel_registerName("helloWord"))
[perosn helloWord];
struct lw_objc_super{
id receiver;
Class super_class;
};
struct lw_objc_super lw_super;
lw_super.receiver = perosn;
lw_super.super_class = [LWAllPerson class];
objc_msgSendSuper(&lw_super, sel_registerName("helloWord"));
}
return 0;
}
Copy the code
2021- 0626 - 19:21:05.047243+0800 objc_msgSend[6976:329613I am the superclass method2021- 0626 - 19:21:05.047989+0800 objc_msgSend[6976:329613I am the superclass methodCopy the code
[perOSN helloWord] is the same as sending messages directly to the parent class through objc_msgSendSuper. Objects of a subclass may call methods of their parent class. Guess: method calls, first in this class, if not in the superclass
objc_msgSend
The assembler to explore
Exploring objc_msgSend starts by finding the underlying library where objc_msgSend resides. How do I find it? Must come up with yYSD-assembler
The compilation shows objc_msgSend in the libobjc.a. dylib system library, actually looking at the prefix objc_msgSend is objc guess should be in the objc source code. Search objc_msgSend globally in the objc source code and find objc-msg-arm64.s, the assembly of the real machine
objc_msgSend
Assembly entry
The following assembly uses p0-P17, and you may be familiar with x0 and X1 in assembly as registers. P0-p17 is just redefining x0 minus x17
Determine whether receiver equals nil, in order to determine whether the Taggedpointer small object type is supported
- support
Taggedpointer
Small object type, small object is empty, returnnil
, not fornil
To deal withisa
To obtainclass
jumpCacheLookup
process - Does not support
Taggedpointer
Small object type andreceiver
=nil
Jump,LReturnZero
The process to returnnil
- Does not support
Taggedpointer
Small object type andreceiver
! =nil
Through theGetClassFromIsa_p16
To get to theclass
Stored in ap16
Register, and then goCacheLookup
process
GetClassFromIsa_p16
To obtainClass
GetClassFromIsa_p16 Core function Specifies that the class is stored in register P16
ExtractISA
// A12 + iPhone X +
#if __has_feature(ptrauth_calls).#else. .macro ExtractISAand $0, $1, #ISA_MASK $0 = $1(isa) & ISA_MASK = class
.endmacro
// not JOP
#endif
Copy the code
The main ExtractISA functions isa & ISA_MASK = class are stored in register P16
CacheLookup
process
buckets
And the subscriptindex
Source code analysis: the first is based on different architectural judgment, the following are real machine as an example. Above this section of source code is mainly donethree
thing
- To obtain
_bucketsAndMaybeMask
The address is going to becache
Address:p16
=isa
(class
),p16
+0x10
=_bucketsAndMaybeMask
=p11
- To obtain
buckets
The address is the first address of the cache memory:buckets
= ((_bucketsAndMaybeMask
>>48
) – 1) - To obtain
hash
The subscript:p12
=(CMD ^ (_cmd >> 7)) & msak
The purpose of this step is to obtainhash
The subscriptindex
- The process is as follows:
isa
–>_bucketsAndMaybeMask
–>buckets
–>hash
The subscript
Traverse the cache
- According to the subscript
index
findindex
The correspondingbucket
.p13
=buckets
+((_cmd ^ (_cmd >> 7)) & mask)
<<(1+PTRSHIFT))
- So let’s get the corresponding
bucket
Then remove themimp
andsel
Deposit top17
andp9
And then*bucket--
To move forward 1
Process:p9
=sel
And the parameters passed in_cmd
Compare. If they go equally2
Flow if not equal walk3
process2
Process: Cache hits direct jumpCacheHit
process3
Process: Judgmentsel
=0
Whether the condition is true. If true, indicatebuckets
There are no arguments passed in_cmd
Cache, there is no need to go down directly jump__objc_msgSend_uncached
Process. ifsel
! =0
Illustrate thisbucket
It’s occupied by another method. I want you to find the next seat and see if it’s what you need. And then in judging the next positionbucket
And the firstbucket
Address size, if greater than the firstbucket
Address jump1
Process loop lookup, if less than or equal to continue the following process- So if we go to number one
1
abucket
I didn’t find a match in any of them_cmd
. So it’s going to keep going down because of the subscriptindex
There may be more to comebucket
No query yet
CacheHit process
CacheHit \Mode
theMode
= NORMAL
TailCallCachedImp
Is aThe macro
.Macro definition
The following
// A12 + iPhone X +
#if __has_feature(ptrauth_calls).#else
.macro TailCallCachedImp
$0 = cached IMP, $1 = buckets, $2 = SEL, $3 = class
eor $0, $0, $3 $0 = imp ^ class; $0 = imp ^ class; $0 = imp ^ class
br $0 / / call the imp
.endmacro
...
#endif
Copy the code
After the cache query, the IMP of the bucket is decoded directly. Imp = IMP ^ class, and then call the decoded IMP
Iterate over the cache flowchart
Question: Why do you want to determine sel = 0 in the bucket, equal to 0, and then directly look up the cache process
- If neither
hash
There is no cache of the target method, sohash
The subscriptbucket
It’s empty and it jumps out of the cache - There will be no middle space
bucket
We have targets on both sidesbucket
This kind of situation
mask
Traverse the cache forward
If the cache is not searched forward, it will jump tomask
The correspondingbucket
Keep looking
- Find the last one
bucket
Location:p13
=buckets
+(mask << 1+3)
Find the last onebucket
The location of the - So let’s get the corresponding
bucket
Then remove themimp
andsel
Deposit top17
andp9
And then*bucket--
To move forward p9
=sel
And the parameters passed in_cmd
Compare. If they go equally2
process- If not equal in the judgment (
sel
! =0
&&bucket
“> < p style =” max-width: 100%hash
The subscriptbucket
) then the loop cache lookup, if the entire process loop still no query or encounter emptybucket
. Indicates that there is no cache in the cache.sel
=_cmd
Method to cache queries to end the jump__objc_msgSend_uncached
process mask
The forward traversal logic is basically the same as the previous loop traversal logic
Cache Query Flowchart
conclusion
Explore the bottom found a problem, is that the bottom of each content is very complex, a lot of calculation and judgment. It’s not like you call a method on top and it looks easy. As the saying goes, the more complicated things are, the more complicated things are. I’m complicated on the surface.