IOS martial arts esoteric article summary

Writing in the front

The cache_t cache is about methods. We’ve looked at the cache write process. Before the write process, there are two cache read processes: objc_msgSend and cache_getImp. So what’s the method? It all starts with Runtime…

A possible secret Demo for this section

A, the Runtime

① What is Runtime?

Runtime is a set of APIS written in C, C ++, and assembly that provide a Runtime for OC.

  • Runtime: The code runs and loads the executable into memory
  • Compile time: the time at which compilation is underway – translating source code willOC, Swift,) into machine language (assembly, etc.) and finally into binary

(2) the Runtime version

The Runtime comes in two versions — Legacy and Modern — and the Apple developer documentation is well-documented

-old and __OBJC__ represent Legacy versions, and -new and __OBJC2__ represent Modern versions, so as to make compatibility

③ Function and call of Runtime

RuntimeThe underlying compilation provides a setAPIAnd for theFrameWork,Serviceuse

Runtime call:

  • Runtime API, such assel_registerName().class_getInstanceSize
  • NSObject API, such asisKindOf()
  • OCUpper-level mode, such as@selector()

Runtime Runtime Runtime Runtime Runtime Runtime Runtime Runtime Runtime Runtime

Second, the nature of the method

① Research methods

You can see the underlying code and get the essence of the method by compiling it into a CPP file with clang

  • Compatible compilation (less code) :clang -rewrite-objc main.m -o main.cpp
  • Full compilation (no error) :xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpporxcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

② Code conversion

  • ((TCJPerson *(*)(id, SEL))(void *)It is type strong
  • (id)objc_getClass("TCJPerson")To obtainTCJPersonClass object
  • sel_registerName("alloc")Is equivalent to@selector()

Objc_msgSend (object, method call)

③ The nature of the method

The essence of a method is to send a message via objc_msgSend, where id is the message receiver and SEL is the method number.

Note: If a C function is externally defined and called like void sayHello() {}, it is still sayHello() after clang is compiled instead of being called via objc_msgSend. Because sending messages is the process of finding function implementation, and C functions can be found by function name – pointer.

To verify, passobjc_msgSendMethod to accomplish[person sayHello]To see if it prints consistently.The printed results are as follows, and they are found to be consistent, so[person sayHello]Is equivalent toobjc_msgSend(person,sel_registerName("sayHello"))

There are two points to note:

  • 1. Direct callobjc_msgSend, need to import the header file#import <objc/message.h>
  • 2, need toTarget --> Build Setting --> search MSG -- change enable strict checking of obc_msgSend calls from YES to NO, turn off the draconian inspection mechanism, otherwiseobjc_msgSendThe argument to the

④ Send messages to different objects

Subclass TCJTeacher has instance methods sayHello, sayNB and class method sayNC

TCJPerson has instance methods sayHello, sayCode, and class methods sayNA

① Send instance method

Message receiver —Instance objects

② Send the class method

③ Object method call – the actual execution is the implementation of the parent class

Notice the previous detail: The parent TCJPerson implements the sayHello method, while the subclass TCJTeacher does not. Now we can try to have teacher call sayHello to implement the parent class implementation through objc_msgSendSuper.

becauseobjc_msgSendCannot send a message to the parent classobjc_msgSendSuperAnd giveobjc_superStructure assignment (inobjc2All you need to do is assignreceiver,super_class)

The receiver –Instance objects; Super_class –Parent class Class object Found that no matter[teacher sayHello]orobjc_msgSendSuperAll execute in the parent classsayHelloSo here, we can make a guess:Method calls, first look in the class, if not found in the class, look in the class's parent class.

④ Send instance methods to the parent class

The receiver –Instance objects; Super_class –Parent class Class object

Send class methods to the parent class

The receiver –Class object; Super_class –Superclass metaclass object

Message search process

Message search process is actually through the upper method number SEL send message objc_msgSend find specific imp implementation process

Objc_msgSend is written in sinks. Why C is not written in sinks is because:

  • The C languageYou can’t jump to arbitrary Pointers by writing a function that keeps unknown parameters while assembly has registers
  • For some functions or operations that are called too frequently, assembly can improve efficiency and performance and be easily recognized by machines

① Start searching

Open theobjc4Source code, as the main studyArm64 structureThe assembly implementation comes toobjc-msg-arm64.s, first attached its compilation of the overall implementation of the flow chart

P0 represents the pointer to register 0, x0 represents its value, and w0 represents the lower 32 bits (don’t worry too much).

  • 1) startobjc_msgSend
  • (2) to determineMessage receiverIf the value is null, the value is returned
  • (3) to determinetagged_pointers(More on that later)
  • ④ Get the objectisaA to savep13In the
  • 5. According toisaformaskThe address offset gets the correspondingThe superior object(Class, metaclass)

See the GetClassFromIsa_p16 definition, which is basically isa & mask to get the class operation

  • ⑥ Start searching in the cacheimp— Started a quick process

② Quick search process

fromCacheLookupA quick lookup process begins (at this pointx1issel.x16isclass)

  • In objc_class, the first address is 16 bytes away from the cache, that is, isa is 8 bytes away from the cache, superClass is 8 bytes away from the cache. In the cache, the top 16 bits are used to store mask and the bottom 48 bits are used to store buckets. The p11 = cache

  • Secondly, pull buckets and mask from the cache, and then use mask to calculate the hash subscript according to the hash algorithm

    • throughcacheandMask (i.e. 0x0000FFFFFFFFFFFF)the&Operation, will be highWipe out the 16-bit mask,bucketsPointer address, i.ep10 = buckets
    • willcacheMoves to the right48A,mask, i.e.,p11 = mask
    • willobjc_msgSendThe parameters of thep1(that is, the second parameter _cmd)& msakThrough theThe hash algorithmAnd get the storage to look forsel-impthebucketThe subscriptindex, i.e.,p12 = index = _cmd & mask“Why in this way? Because in theStore the sel - impIs also through the sameThe hash algorithm computes the hash subscript for storage, soreadYou also need to passRead in the same way, as shown below
  • ③ According to the first address of index and buckets, extract the bucket corresponding to the hash index

    • Among themPTRSHIFTIs equal to the3The shift to the leftfour(i.e.2 ^ 4 = 16The object of byte) is to compute onebucketThe actual size of the occupied structurebucket_tIn theselAccount for8Bytes,impAccount for8byte
    • According to the calculated hash indexindexMultiplied by a singleMemory size occupied by a bucket,bucketsThe first address inReal memoryIn theThe offset
    • throughStart address + actual offsetGets the hash subscriptindexThe correspondingbucket
  • ④ According to the obtained bucket, remove the IMP into P17, that is, P17 = IMP, remove SEL into P9, that is, P9 = SEL

  • ⑤ The first recursive loop

    • Comparably acquiredbucketIn theselobjc_msgSendOf the second parameter of_cmd(that is, p1) is equal
    • If they are equal to each other, jump toCacheHit, that is, cache hitimp
    • If they are not equal, there are two situations
      • If you cannot find it all the time, jump toCheckMissBecause the$0isnormal, jumps to__objc_msgSend_uncached, that is, to enterSlow search process
      • If according toindexTo obtain thebucketIs equal to thebucketsThe first element of the, will be artificially presentbucketSet tobucketsThe last element (passesMove the first address of buckets +mask 44 bits to the right(equivalent to moving 4 places to the left) directLocate the last element of the bucker), and continue the recursive loop (The first oneRecursive loop nestingThe secondRecursive loop), namely ⑥
      • If the currentbucketIs not equal tobucketsThe first element of, continuesLook aheadAnd into theFirst recursive loop
  • ⑥ The second recursive loop: The only difference is that if the bucket is the first element equivalent to buckets, the bucket goes directly to JumpMiss, where $0 is normal and __objc_msgSend_uncached

Here’s the wholeQuickly findprocessThe change of the valueProcess flow chart

Slow search process

Slow search – assembly part

In the quick lookup process, if the method implementation is not found, both CheckMiss and JumpMiss end up with the __objc_msgSend_uncached assembly function

  • inobjc-msg-arm64.sFind in file__objc_msgSend_uncachedThe assembly implementation, the core of which isMethodTableLookup(that is, list of query methods), its source code is as follows

  • searchMethodTableLookupThe assembly implementation, the core of which is_lookUpImpOrForward, assembly source code implementation as follows

The procedure for verifying the above assembly can be verified by assembly debugging

  • inmain, such as[person sayHello]Object method call with a breakpoint, and enable assembly debuggingDebug -- Debug worlflow -- Always show Disassembly, run the program
  • In the assemblyobjc_msgSendAdd a breakpoint, perform the break, holdcontrol + stepintoAnd into theobjc_msgSendThe assembly of
  • in_objc_msgSend_uncachedAdd a breakpoint, perform the break, holdcontrol + stepinto, enter assembly

As you can see from above, lookUpImpOrForward is the last thing that comes up, and it is not an assembly implementation.

Pay attention to

  • 1.C/C++In the callassemblyTo go toFind assembly.C/C + + callThe method requiresAdd an underscore
  • 2,assemblyIn the callC/C++Method, to findC/C++Method that needs to be called by the assemblyRemove an underscore

② Slow search -c /C++ part

Follow the instructions in the assembly section to continue the search globallylookUpImpOrForwardAnd finally theobjc-runtime-new.mmFile found in the source code implementation, this is acImplemented functionThe overall slow search process is shown in figure 1The slow process is mainly divided into several steps:

  • 1.cacheTo find in the cache, i.eQuickly find, returns directly if foundimp, otherwise, enter ②
  • (2) determine the CLS
    • Whether it isKnown class, if not, thenAn error
    • Whether the classimplementation, if not, it needs to be implemented first to determine the parent class chain. At this time, the purpose of instantiation is to determine the parent class chain, RO, and RW, so as to facilitate the subsequent data reading and search cycle
    • Whether or notInitialize theIf not, the system initializes
  • 3.forCycle, according toClass inheritance chainorMetaclass inheritance chainSequential search of
    • The currentclsIs used in the list of methodsBinary search algorithmFind the method, if found, thenThe cache write process is started(in theIOS: Cache_t analysisAs detailed in this article), and returnsimp, if not found, returnsnil
    • The currentclsTo be an assignmentThe parent classIf the parent class is equal tonil,Imp = message forwardingAnd terminate the recursion into ④
    • ifThe chain of the parent classincycle, an error is reported,Terminate the loop
    • The parent class cacheSearch method in
      • ifNot found, directly returnsnilAnd continue toLoop through
      • iffind, directly returnsimp, the implementation ofCache writesprocess
  • (4)judgeWhether it has been executedDynamic method parsing
    • ifThere is no, the implementation ofDynamic method parsing
    • ifExecuted onceDynamic method resolution, then go toMessage Forwarding Process

The above is the slow search process of the method. The principle of binary search and the detailed steps of the parent class cache search are explained in detail below

GetMethodNoSuper_nolock method: List of binary lookup methods

The process for finding a list of methods is shown below

Its binary search core source code implementation is as follows Algorithm principleShort description: from the first search, each fetchMiddle position, and want to findThe value of the key valuesIn comparison, ifequal, you need toExclusion classification methodAnd then the method implementation of the queried location is returned ifNot equal to the, you need toContinue binary search, if the loop tocount = 0If not found, return directlynil, as follows:

To findTCJPersonOf the classsayHelloExample method, the binary search process is as follows

Cache_getImp method: superclass cache lookup

cache_getImpAnd the way to do that is byAssembly _cache_getImpImplementation, passed in$0GETIMP, as shown below

  • ifThe parent class cacheIs found, jump toCacheHitIf yes, return directlyimp
  • If theThe parent class cache,Could not findMethod, jump toCheckMissorJumpMissBy judgment$0Jump toLGetImpMiss, return directlynil.

conclusion

  • forObject methods (that is, instance methods)In which theClass lookup, its slow searchThe chain of the parent classIs this:Class -- parent class -- root class --nil
  • forClass methodIn which theIn the metaclass, its slow searchThe chain of the parent classIs this:Metaclass -- root metaclass -- root class --nil
  • ifFast search, slow searchAlso did not find a way to implement, then tryDynamic method resolution
  • ifDynamic method resolutionIf still not found, proceedforward
Common methods are not implemented error source code

If no implementation is found in the process of fast lookup, slow lookup, or method resolution, message forwarding is used, and the process is as follows

Message forwarding will be implemented

  • Among them_objc_msgForward_impcacheIs an assembly implementation that jumps to__objc_msgForward, its core is__objc_forward_handler

  • Assembler implementation to find__objc_forward_handler, and not found in the source codeRemove an underscoreGlobal search_objc_forward_handler, has the following implementation, which is essentially calledobjc_defaultForwardHandlermethods

If objc_defaultForwardHandler looks familiar, this is the most common error we see in everyday development: not implementing a function, running an application, crashing.

🌰 : defineTCJPersonSuperclass, where there aresayNBInstance methods andsayHappayClass method

Define subclasses:TCJStudentClass with instance methodssayHelloandsayMasterThe class methodsayObjc, where the instance methodsayMasterUnrealized.

inmainIn the callTCJStudendInstance method ofsayMasterWhen running the program, an error occurs, indicating that the method is not implemented, as shown below

Now, let’s talk about how to prevent unimplemented crashes of methods before they crash.

Dynamic method analysis

inSlow to findprocessNot foundMethod is first tried once when implementedDynamic method resolution, its source code implementation is as follows:It is mainly divided into the following steps

  • judgeWhether a class is a metaclass
    • If it isclass, the implementation ofInstance methodsDynamic method resolutionresolveInstanceMethod
    • If it isThe metaclass, the implementation ofClass methodDynamic method resolutionresolveClassMethod, if in a metaclassCould not findOr foremptyWhile the,The metaclasstheInstance methodsDynamic method resolutionresolveInstanceMethodIn the search, mainly becauseClass methods are instance methods in metaclasses, so you also need to look for dynamic method resolutions for instance methods in the metaclass
  • ifDynamic method resolution, itThe implementation points to other methods, then continueFinds the specified IMP, that is, continue the slow searchlookUpImpOrForwardprocess

The process is as follows

① Example method

forInstance methodsCall, when the implementation of the instance method is not found in both fast and slow lookups, we have a salvage opportunity to try onceDynamic method resolution, because it isInstance methodsSo it goes toresolveInstanceMethodMethod, whose source is as followsIt is mainly divided into the following steps:

  • It is sentresolveInstanceMethodBefore a message, you need to look upclsClass if there is an implementation of the method, that is, throughlookUpImpOrNilMethods will come in againlookUpImpOrForwardSlow search process searchresolveInstanceMethodmethods
    • If not, return directly
    • If yes, send itresolveInstanceMethodThe message
  • Again slowly find the implementation of the instance method, that is, throughlookUpImpOrNilMethods will come in againlookUpImpOrForwardSlow lookup process Lookup instance method

② Crash modification — dynamic method resolution

forExample method say666Unimplemented error reporting crashes can be passed inclassThe rewriteresolveInstanceMethodClass method and point it to an implementation of another method, namely, inTCJPersonThe rewriteResolveInstanceMethod class methodThat will beExample method say666Implementation direction ofsayMasterMethod implementation as shown below

If we were in theresolveInstanceMethodIn a class method, it doesn’t point to another method’s implementation, it comes twice. Why is that? We’ll explain later…

(3) class method

forClass method, like instance methods, can also be overriddenresolveClassMethodClass method to solve the previous crash problem, namely, inTCJPersonClass, and willsayNBThe implementation of a class method points to a class methodsayHappy resolveClassMethodClass method overrides need to be aware of one thing passed inCLS is no longer a class.But a metaclass, can be accessed throughobjc_getMetaClassMethod gets the metaclass of the class becauseBecause class methods are instance methods in metaclasses.

④ Optimization scheme

Is there a better way to do this once and for all? In fact, it can be found that there are two search paths through the method slow search process

  • Instance methods: class — parent class — root class — nil
  • Class methods: metaclass — root metaclass — root class — nil

What they all have in common is that if you don’t find them, they all come to youThe root class is NSObjectSo can we integrate the above two methods together? The answer is yes, yesNSObject Adds a classThe way toRealize unified processingAnd because the class method lookup, in its inheritance chain, lookup is also the instance method, so you can put the unified treatment of instance method and class method inresolveInstanceMethodMethod, as shown belowThe implementation of this way, just with the source code for the method of class processing logic is consistent, that is, the perfect explanation why to call the method of class dynamic method resolution, but also call the object method dynamic method resolution, the fundamental reason is the method of class in the metaclass is instance method.

Above this kind of writing, of course, there will be other problems, such as system method also can be changed, for this, can be optimized, namely we can according to the custom class method unified method name prefix, judging by the prefix is a custom method, and then unified handling custom methods, for example can pop before collapse to the home page, It is mainly used for anti-crash processing of APP online to improve user experience.

⑤ Summary of dynamic method resolution

  • Instance methodsCan be rewrittenresolveInstanceMethodaddimp
  • Class methodCan be overridden in this classresolveClassMethodtoThe metaclassaddimpOr, inNSObject classificationrewriteresolveInstanceMethodaddimp
  • Dynamic method parsingAs long as you take any steplookUpImpOrNilFind theimpI’m not going to look it upThis classDo the dynamic method resolution, don’t go toNSObjct classificationDynamic method resolution
  • All methods are available through theNSObject classificationrewriteresolveInstanceMethodaddimpTo solve the collapse

Wouldn’t it be nice to process all crashes in NSObjct classification, prefixed to separate business logic? Wrong!

  • Unified processing of high coupling degree
  • Multiple logical judgments
  • May be inNSObjct classificationThe dynamic method resolution has been dealt with before
  • SDKWhen encapsulating, you need to give error tolerance

Therefore, the previous ④ optimization scheme is not the most perfect solution. Well, it doesn’t work, it doesn’t work, so what do we do? Don’t worry, Apple dad has a plan for us!

5. Message forwarding mechanism

LookUpImpOrForward: If fast + slow does not find a way to implement it and dynamic resolution does not work, we use message forwarding. However, we find no source code for message forwarding. What methods went through before the method call crashed

  • throughinstrumentObjcMessageSendsMode Displays logs about sending messages

instrumentObjcMessageSends

Through lookUpImpOrForward – > log_and_fill_cache – > logMessageSend, found at the bottom of the logMessageSend source instrumentObjcMessageSends source code to achieve, so, In the main call instrumentObjcMessageSends print method call log information, has the following two preparations

  • 1, open the objcMsgLogEnabled switch, namely call instrumentObjcMessageSends method, introduced to YES

  • 2, inmainThrough theexternThe statementinstrumentObjcMessageSendsmethods

  • throughlogMessageSendSource code, read the message sent to print information stored in/tmp/msgSendsDirectory, as shown below

  • Run the code, go to the/TMP /msgSends directory, find a log file that starts with msgSends, open it, and execute the following method before crashing

    • Two dynamic method resolutions:resolveInstanceMethodmethods
    • Fast forwarding of two messages:forwardingTargetForSelectormethods
    • Slow forwarding of two messages:methodSignatureForSelector + resolveInvocation

Fast forwarding process

ForwardingTargetForSelector only a statement in the source code, and no other description, mentioned in the documentation to explain about it:

  • The return object of this method is executionselThe message will be forwarded to other objects for processing by related methods, but cannot be returnedselfOr you’ll never find it
  • The method is more efficient, if not implemented, will go toforwardInvocation:Method to process
  • The underlying callsobjc_msgSend(forwardingTarget, sel, ...) ;To realize the sending of messages
  • The receiver parameters, return values, etc. of the forwarded message should be the same as the original method

Fast forwarding process resolves crashes

The following code resolves crashes by fast forwarding — i.eTCJPersonMethods that can’t be implemented, forward toTCJStudentTo implement (forward to objects that already implement the method)

You can also specify no message receiver,Call the method directly from the parent class, if still not found,Direct error

Slow forwarding process

When the fast forwarding process fails to find the forwarding object, the slow forwarding process startsmethodSignatureForSelectorFollow suit and find it in the help filesmethodSignatureForSelectorClick to viewforwardInvocation

  • forwardInvocationandmethodSignatureForSelectorIt has to be simultaneous, and the bottom layer will generate one by signing the methodNSInvocationPass it as an argument to the call
  • Lookup can respondNSInvocation(This object does not have to be the same for all messages)
  • useanInvocationThe message is sent to the object.anInvocationThe results are saved, and the run-time system extracts the results and passes them to the original sender

Slow forwarding process resolution crash

Slow forwarding processIs the firstmethodSignatureForSelectorProvide a method signature and thenforwardInvocationBased on theNSInvocationTo realize the forwarding of messages

You could be rightforwardInvocationIn the methodinvocationNo processing, and no crash error

So, from the above, the program does not crash whether the forwardInvocation method handles the invocation transaction or not.

Through the Hopper /IDA disassembly message forwarding mechanism

Hopper and IDA are tools that help us statically analyze visibility files, disassemble executable files into pseudocode, control flow diagrams, and so on, using Hopper as an example.

  • Run program crash, view stack information

  • found___forwarding___fromCoreFoundation

  • throughimage list, read the entire image file, and then searchCoreFoundationTo view the path to its executable file

  • Go to the file pathCoreFoundationExecutable file

  • Open thehopper, the choice ofTry the DemoThen drag in the executable from the previous stephopperTo disassemble, selectx86(64 bits)

  • The following is the interface after disassembly, mainly using the above three functions, respectively assembly, flow chart, pseudocode

  • Search for __forwarding_prep_0___ through the search box on the left, then select the pseudocode

  • The following is a__forwarding_prep_0___Assembler pseudocode, jump to___forwarding___

  • The following is a___forwarding___Pseudo-code implementation, the first is to see if it is implementedforwardingTargetForSelectorIf there is no response, jump toloc_6459bThat is, fast forwarding does not respond and the slow forwarding process enters

  • Jump toloc_6459bBelow it to determine whether to respondmethodSignatureForSelectormethods

  • If there is no response, jump to loc_6490b, an error is reported

  • If you getmethodSignatureForSelectorThe method is signed asnil, is also a direct error

  • ifmethodSignatureForSelectorIf the return value is not empty, theforwardInvocationMethod forinvocationFor processing

Through the above two search methods can be verified, there are three methods of message forwarding

  • Fast forward forwardingTargetForSelector 】
  • [Slow forwarding]
    • methodSignatureForSelector
    • forwardInvocation

The overall process of message forwarding is as follows

The processing of message forwarding is divided into two parts:

  • [Fast forwarding] When slow search and dynamic method resolution are not found, message forwarding is carried out. First, fast message forwarding is carried out, that is, go toforwardingTargetForSelectormethods
    • If the returnMessage receiverIf no method implementation is found in the message receiver, another method lookup process is entered
    • If nil is returned, slow message forwarding is entered
  • [Slow forwarding] TomethodSignatureForSelectormethods
    • If the returnedThe method signaturefornilThe directCollapse of the error
    • If the returnedThe method signatureDon’t fornilAnd went toforwardInvocationMethod, rightinvocationTransaction processing,No error is reported if no processing is performed

Why are dynamic method resolutions executed twice?

The dynamic method resolution method mentioned in the previous article has been implemented twice, with the following two ways of analysis

Explore god’s perspective

In the slow search process, we learned thatresolveInstanceMethodMethod is executed throughlookUpImpOrForward --> resolveMethod_locked --> resolveInstanceMethodCame toresolveInstanceMethodSource code, in the source code by sendingresolve_selMessage firing, as shown belowSo you can findresolveInstanceMethodIn the methodIMP imp = lookUpImpOrNil(inst, sel, cls);A breakpoint is added to passbtprintThe stack informationSo what’s going on

  • inresolveInstanceMethodIn the methodIMP imp = lookUpImpOrNil(inst, sel, cls);Add a breakpoint and run the program until the first “come” passesbtTo viewFirst dynamic method resolutionStack information at this timeselissay666

  • Continue until the second “coming” print, looking at the stack information, in the second, we can see that is throughCoreFoundationthe-[NSObject(NSObject) methodSignatureForSelector:]Method, and then throughclass_getInstanceMethodEnter dynamic method resolution again

  • We need to look at the stack information from the previous stepCoreFoundationWhat did The Chinese do? throughHopperThe disassemblyCoreFoundationThe executable file to viewmethodSignatureForSelectorMethod pseudocode

  • throughmethodSignatureForSelectorPseudocode entry___methodDescriptionForSelectorThe implementation of the

  • Enter the___methodDescriptionForSelectorThe pseudo-code implementation, combined with the assembled stack print, can be seen in___methodDescriptionForSelectorThat’s called in this methodObjc4 sourcetheclass_getInstanceMethod

  • inObjc4-818.2 -Source searchclass_getInstanceMethod, the source code implementation is shown below

This can be verified by code debugging, as shown below, inclass_getInstanceMethodAdd a breakpoint to the method and executemethodSignatureForSelectorAfter the method, the signature is returned, indicating that the method signature is in effect, and apple is walking toinvocationBefore, developers were given a chance to query again, so go toclass_getInstanceMethodHere, go to the method query againsay666And then it goes to the dynamic method resolution again

Therefore, the above analysis also confirms the reason why the resolveInstanceMethod method was executed twice

Exploration without a God’s perspective

If there is no God perspective, we can also use the code to deduce where the dynamic method resolution is called again

  • TCJPersonClass to rewriteresolveInstanceMethodMethod, and plusclass_addMethodOperating theAssignment IMPAt this time,resolveInstanceMethodWill you go twice?

If an IMP is assigned, the dynamic method resolution will only go once, indicating that it is not the second dynamic method resolution

Keep exploring

  • To get rid ofresolveInstanceMethodMethodIMPIn theTCJPersonClass to rewriteforwardingTargetForSelectorMethod and specify that the return value is[TCJStudent alloc], rerun ifresolveInstanceMethodIt was printed twice, which means it wasforwardingTargetForSelectorMethod executes the dynamic method resolution before, and vice versaforwardingTargetForSelectorMethods after

Results found resolveInstanceMethod in print or print only once, that means the second resolution after forwardingTargetForSelector method dynamic method

  • inTCJPersonClass to rewritemethodSignatureForSelectorforwardInvocationRun,

The results showed that the second method of dynamic resolution between methodSignatureForSelector and forwardInvocation method.

The second analysis also supports the foregoingresolveInstanceMethodThe reason why it was executed twice. After the above argument, we know that in the process of slow message forwarding, inmethodSignatureForSelectorforwardInvocationThere is also a dynamic method resolution between methods, which Apple gives a chance to do again, as shown in the figure below

Write in the back

So far, the objc_msgSend message sending process has been analyzed and summarized here

  • [Quick search process]First, in the classBuffer cacheTo find the implementation of the specified method
  • Slow search processIf it is not found in the cache, theClass method listIf still not found, go toA list of caches and methods for the parent class chainLook for
  • [Dynamic Method resolution]If a slow search doesn’t find anything,First chance to fix itJust try it onceDynamic method resolution, that is, to rewrite theresolveInstanceMethod/resolveClassMethodmethods
  • [Message forwarding]If the dynamic method resolution is still not found, proceedforwardThere are two remedial opportunities in message forwarding:Fast forwarding + Slow forwarding
  • If not, the program will crashunrecognized selector sent to instance

Finally, learn harmoniously without being impatient. I’m still me, a different color of fireworks.