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

RuntimeIntroduction to the

What’s the difference between Runtime, which is commonly called Runtime, and compile time, which is commonly called compile time

  • Compile timeWhat 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 checking
  • The 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

Runtimeversion

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 forObjective - 1.0 C, 32 bitsMac OS XThe platform of
  • Current version used forObjective - 2.0 C, iPhone apps andMac OS X v10.564-bit programs on and after systems

RuntimeCall in three ways

  • Objective-CWay,[penson sayHello]
  • Framework & SerivceWay,isKindOfClass
  • Runtime APIWay,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 downobjc_msgSendCheck mechanism:target –> Build Setting– > searchobjc_msgSend Enable strict checking of obc_msgSend callsSet 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

clangthemain.mgeneratemian.cppFile, querymainImplementation 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_msgSendThe 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_msgSendAssembly 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

  • supportTaggedpointerSmall object type, small object is empty, returnnil, not fornilTo deal withisaTo obtainclassjumpCacheLookupprocess
  • Does not supportTaggedpointerSmall object type andreceiver = nilJump,LReturnZeroThe process to returnnil
  • Does not supportTaggedpointerSmall object type andreceiver! =nilThrough theGetClassFromIsa_p16To get to theclassStored in ap16Register, and then goCacheLookupprocess

GetClassFromIsa_p16To 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

CacheLookupprocess

bucketsAnd 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 donethreething

  • To obtain_bucketsAndMaybeMaskThe address is going to becacheAddress:p16 = isa(class),p16 + 0x10 = _bucketsAndMaybeMask = p11
  • To obtainbucketsThe address is the first address of the cache memory:buckets= ((_bucketsAndMaybeMask >> 48) – 1)
  • To obtainhashThe subscript: p12 =(CMD ^ (_cmd >> 7)) & msakThe purpose of this step is to obtainhashThe subscriptindex
  • The process is as follows:isa –> _bucketsAndMaybeMask –> buckets –>hashThe subscript

Traverse the cache

  • According to the subscriptindexfindindexThe correspondingbucket.p13 = buckets + ((_cmd ^ (_cmd >> 7)) & mask) << (1+PTRSHIFT))
  • So let’s get the correspondingbucketThen remove themimpandselDeposit top17andp9And then*bucket--To move forward
  • 1Process:p9= selAnd the parameters passed in_cmdCompare. If they go equally2Flow if not equal walk3process
  • 2Process: Cache hits direct jumpCacheHitprocess
  • 3Process: Judgmentsel = 0Whether the condition is true. If true, indicatebucketsThere are no arguments passed in_cmdCache, there is no need to go down directly jump__objc_msgSend_uncachedProcess. ifsel! =0Illustrate thisbucketIt’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 positionbucketAnd the firstbucketAddress size, if greater than the firstbucketAddress jump1Process loop lookup, if less than or equal to continue the following process
  • So if we go to number one1abucketI didn’t find a match in any of them_cmd. So it’s going to keep going down because of the subscriptindexThere may be more to comebucketNo query yet

CacheHit process

CacheHit \ModetheMode = NORMAL TailCallCachedImpIs aThe macro.Macro definitionThe 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 neitherhashThere is no cache of the target method, sohashThe subscriptbucketIt’s empty and it jumps out of the cache
  • There will be no middle spacebucketWe have targets on both sidesbucketThis kind of situation

maskTraverse the cache forward

If the cache is not searched forward, it will jump tomaskThe correspondingbucketKeep looking

  • Find the last onebucketLocation:p13 = buckets + (mask << 1+3)Find the last onebucketThe location of the
  • So let’s get the correspondingbucketThen remove themimpandselDeposit top17andp9And then*bucket--To move forward
  • p9= selAnd the parameters passed in_cmdCompare. If they go equally2process
  • If not equal in the judgment (sel! =0 && bucket“> < p style =” max-width: 100%hashThe 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 = _cmdMethod to cache queries to end the jump__objc_msgSend_uncachedprocess
  • maskThe 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.