The preparatory work

The following clang command is used to compile the actual code into the corresponding CPP implementation file in the current directory, which is convenient for studying the nature of OC objects

Clang-rewrite-objc [file.m] -o [CPP]Copy the code

The following methods are written in main, where LSStudent has an object method run and a class method walk

LSStudent *s = [LSStudent new];
[s run];
[LSStudent walk]
Copy the code

If you look at main.cpp compiled with Clang, you’ll see that the direct call logic for these lines of code calls the method as follows

The objc_msgSend function, in fact, the essence of the method is to call objc_msgSend to send a message to the specified object or the specified class. In this case, it uses objc_msgSend to send a message. The objc_msgSend has two basic parameters. The second is the method of execution

As you can see from the compiled code below, calling object methods and class methods send messages to objects and class objects, respectively. You can already see from exploring ISA that object methods are in classes, and class methods are stored in metaclasses. You can see that the method calls that call objc_msgSend to send the specified message come from their ISA

The objc_msgSend method in runtime is declared in the message header, Id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op,...) // init, the alloc&&init mentioned earlier, LSStudent *s = ((LSStudent *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LSStudent")), sel_registerName("new")); / / send a message to objects (void (id, SEL)) (*) (void *) objc_msgSend) ((id) s, sel_registerName (" run ")); // Send a message to LSStudent ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LSStudent"), sel_registerName("walk"));Copy the code

You can guess the method implementation imp is of the following form, with the first argument being the receiving message object and the second argument being the method

id objImp(id self, SEL _cmd) {
}
Copy the code

Objc_msgSend and objc_msgSendSuper are two c functions commonly used to send messages. They can be divided into sending messages to an object and sending messages to a parent class. When sending messages to a parent class, parameters of objc_super type need to be passed. It is a structure with two parameters, one is the receiver self, and the other is the superClass that receives the message object, which uses self to execute the parent class selector method. The call case code is as follows

Struct objc_super {/// Specifies an instance of a class. __unsafe_unretained _Nonnull ID receiver; __unsafe_unretained _Nonnull Class super_class; }; // Send a message to an object, either an instance object or a class object objc_msgSend(s, sel_registerName("run")); Struct objc_super mySuper; mySuper.receiver = s; mySuper.super_class = class_getSuperclass([s class]); objc_msgSendSuper(&mySuper, @selector(run));Copy the code

Objc_msgSendSuper works in the same way as objc_msgSend. This section describes only objc_msgSend

objc_msgSend

Objc_msgSend is an API written in assembly. Since we usually use the ARM64 architecture, we will explore it here

If you search the objC source file directly, you will find the following description, which describes objc_msgSend

/******************************************************************** * * id objc_msgSend(id self, SEL _cmd, ...) ; * IMP objc_msgLookup(id self, SEL _cmd, ...) ; * * objc_msgLookup ABI: * IMP returned in x17 * x16 reserved for our use but not used * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /Copy the code

Part of the assembly code implementation is shown below

ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame // Compare nil CMP p0, // check and tagged pointer check #if support_tagged_pointer is null // // (MSB tagged pointer looks negative) #else // Tagged pointer is not supported. LReturnZero b.eq LReturnZero #endif ldr p13, [x0] // p13 = isa GetClassFromIsa_p16 p13, 1, X0 // p16 = class // get isa // finally jump to this tag execute LGetIsaDone: // calls IMP or objc_msgSend_uncached //CacheLookup __objc_msgSend_uncached #if SUPPORT_TAGGED_POINTERS // if tagged pointer is nil, LNilOrTagged: // Check whether the LReturnZero is empty. // nil check GetTaggedClass // get class b LGetIsaDone // Jump to LGetIsaDone execute // SUPPORT_TAGGED_POINTERS #endif // Check to see if LReturnZero is nil: X0 is already zero mov x1, #0 movi d0, #0 movi d1, #0 movi d2, #0 movi d3, #0 movi retCopy the code

Through the above source can be seen

Check whether the agged Pointer type is supported. At present, the system basically supports it

Then jump directly to the tag LNilOrTagged, check if the object is nil, and get the class

Jump to the LGetIsaDone TAB, where the logic is listed directly:

Search the cache to find direct imp execution

Not found, go to the class to find whether there is, find add to the cache, and execute, find not ready to forward the message, behind is we are familiar with the C language (that is, went to the non-high-speed search route), that is, from the search IMP began

That brings us to lookUpImpOrForward, but before we introduce it, we’ll look at a class that starts with the _lookUpImpTryCache method, whose source code is listed below

If lookUpImpOrForward is not called directly, imp is called. If it is not, imp is called. If it is in the cache, it is hit

ALWAYS_INLINE static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertUnlocked(); if (slowpath(! cls->isInitialized())) { // see comment in lookUpImpOrForward return lookUpImpOrForward(inst, sel, cls, behavior); } IMP imp = cache_getImp(cls, sel); if (imp ! = NULL) goto done; #if CONFIG_USE_PREOPT_CACHES if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) { imp = cache_getImp(cls->cache.preoptFallbackClass(), sel); } #endif if (slowpath(imp == NULL)) { return lookUpImpOrForward(inst, sel, cls, behavior); } done: if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) { return nil; } return imp; }Copy the code

Next comes the lookUpImpOrForward method, which is the process of going to class to look up the method

NEVER_INLINE IMP lookUpImpOrForward(id inst, SEL sel, Class cls, Int behavior) {const imp forward_IMP = (imp)_objc_msgForward_impcache; IMP imp = nil; Class curClass; runtimeLock.assertUnlocked(); if (slowpath(! cls->isInitialized())) { behavior |= LOOKUP_NOCACHE; } runtimeLock.lock(); // Check whether the checkIsKnownClass(CLS) does not exist; / / check the class initialization, not the initialization CLS = realizeAndInitializeIfNeeded_locked (inst, CLS, behaviors & LOOKUP_INITIALIZE); // runtimeLock may have been dropped but is now locked again runtimeLock.assertLocked(); curClass = cls; Reasonableclasscount ();) {/ / if a cache hit the if (curClass - > cache. IsConstantOptimizedCache strict (/ * * / true)) {# if CONFIG_USE_PREOPT_CACHES imp = cache_getImp(curClass, sel); if (imp) goto done_unlock; curClass = curClass->cache.preoptFallbackClass(); #endif} else {// curClass method list. Sel Method meth = getMethodNoSuper_nolock(curClass, sel); Imp = meth->imp(false); Cache is located to done goto done; } // Locate the parent class, end if the parent class does not exist, Slowpath ((curClass = curClass->getSuperclass()) == nil)) {// implementation found, and method resolver didn't help. // Use forwarding. imp = forward_imp; break; } } // Halt if there is a cycle in the superclass chain. if (slowpath(--attempts == 0)) { _objc_fatal("Memory corruption  in class list."); } // Superclass cache. imp = cache_getImp(curClass, sel); If (slowpath(imp == forward_imp)) {// Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; } if (fastPath (imp)) {// Found the method in a superclass. Cache it in this class.goto done; } // No implementation found. Try method resolver once. If (slowpath(behavior & LOOKUP_RESOLVER)) {behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); } done: if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) { #if CONFIG_USE_PREOPT_CACHES while (cls->cache.isConstantOptimizedCache(/* strict */true)) { cls = cls->cache.preoptFallbackClass(); } #endif log_and_fill_cache(cls, imp, sel, inst, curClass); // fill the cache} done_unlock: runtimelock.unlock (); if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) { return nil; } return imp; Returns the final imp}Copy the code

GetMethodNoSuper_nolock, which goes to the RW’s methods list in class and looks for the specified method.

static method_t * getMethodNoSuper_nolock(Class cls, SEL sel) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); // fixme nil cls? // fixme nil sel? auto const methods = cls->data()->methods(); for (auto mlists = methods.beginLists(), end = methods.endLists(); mlists ! = end; ++mlists) { // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest // caller of search_method_list, inlining it turns // getMethodNoSuper_nolock into a frame-less function and eliminates // any store from this codepath. method_t *m = search_method_list_inline(*mlists, sel); if (m) return m; } return nil; }Copy the code

If no method is found, call resolveMethod_locked. If no method is found, call resolveMethod_locked. If no method is found, call resolveMethod_locked

The implementation of resolveMethod_locked is as follows

static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); if (! CLS - > isMetaClass ()) {/ / try/CLS resolveInstanceMethod: sel / / not a metaclass call the instance of the dynamic analysis method, ResolveInstanceMethod (INST, sel, CLS); resolveInstanceMethod(inst, sel, CLS); } else {/ / try [nonMetaClass resolveClassMethod: sel] / / and/CLS resolveInstanceMethod: sel / / for metaclasses, ResolveClassMethod (inST, sel, CLS); ResolveInstanceMethod (resolveInstanceMethod, resolveInstanceMethod, resolveInstanceMethod) if (! lookUpImpOrNilTryCache(inst, sel, cls)) { resolveInstanceMethod(inst, sel, cls); } } // chances are that calling the resolver have populated the cache // so attempt using it return lookUpImpOrForwardTryCache(inst, sel, cls, behavior); }Copy the code

ResolveInstanceMethod is similar to resolveClassMethod in that it calls the method directly by sending a message to generate the specified method

static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); // check if the resolveInstanceMethod method is implemented. lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) { // Resolver not implemented. return; BOOL (* MSG)(Class, SEL, SEL) = (typeof(MSG))objc_msgSend; bool resolved = msg(cls, resolve_sel, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. IMP IMP = lookUpImpOrNilTryCache(inST, sel, CLS); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); } } } static void resolveClassMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); ASSERT(cls->isMetaClass()); // check if the resolveInstanceMethod method is implemented. lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) { // Resolver not implemented. return; } // If you want to send a message to a metaclass, you need to get a Class object and send it to Class nonmeta. { mutex_locker_t lock(runtimeLock); nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst); // +initialize path should have realized nonmeta already if (! nonmeta->isRealized()) { _objc_fatal("nonmeta class %s (%p) unexpectedly not realized", nonmeta->nameForLogging(), nonmeta); BOOL (* MSG)(Class, SEL, SEL) = (typeof(MSG))objc_msgSend; bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() IMP IMP = lookUpImpOrNilTryCache(inST, SEL, CLS); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code

If the above dynamic parsing still fails, the imp implementation of the previous message forwarding will be called _objc_msgForward_impcache, i.e., message forwarding. In a more concise form, we can look at the methods that went before the crash

Objc/message statement inside the instrumentObjcMessageSends a function, we only need to import the function to the statement, and then call the specified method can, so you can see the method invocation process, implement case as shown below

After instrumentObjcMessageSends code execution, generates a log files saved to the local

Directory: Computer -> Private -> TEM ->msgSend- Digital (this is the generated file)

#import<objc/message.h>
extern void instrumentObjcMessageSends(BOOL);

instrumentObjcMessageSends(YES);
[LSPerson walk];
instrumentObjcMessageSends(NO);
Copy the code

forward

Through the implementation of the above code, the message forwarding process can be led out, as shown in the figure below

The message forwarding process has gone through the above steps altogether, which is another set of defense left by Apple for the majority of developers. Developers can customize the crash or square crash scheme

Dynamically resolve resolveInstanceMethod and resolveClassMethod

According to the above message sending mechanism, when no method can be found, the system will go to resolveInstanceMethod and resolveClassMethod to find the solution. When the method is implemented and successfully invoked, the system will find out whether the solution is really solved from the class. If the solution is not solved, To continue downward or collapse; If a dynamic method adds a method resolution, then it does not crash and the problem is resolved

Dynamic resolution is the first line of defense against crashes. ResolveInstanceMethod and resolveClassMethod are used to resolve crashes

+ (BOOL)resolveInstanceMethod: SEL SEL {resolveInstanceMethod: SEL SEL { If (sel == @selector(run)) {// we dynamically parse our object method NSLog(@" object method parse here "); SEL runSEL = @selector(runReplace); Method runM= class_getInstanceMethod(self, runSEL); IMP runImp = method_getImplementation(runM); const char *type = method_getTypeEncoding(runM); return class_addMethod(self, sel, runImp, type); } return [super resolveInstanceMethod:sel]; } // resolveClassMethod:(SEL) SEL {resolveClassMethod:(SEL) SEL {resolveClassMethod:(SEL) SEL { If (sel == @selector(walk)) {NSLog(@" class method parsing goes here "); SEL walkSEL = @selector(walkRepalce); //Method walkdM1= class_getClassMethod(self, walkSEL); WalkM = class_getInstanceMethod(object_getClass(self), walkSEL); // Get the object method IMP walkImp = method_getImplementation(walkM); const char *type = method_getTypeEncoding(walkM); NSLog(@"%s",type); return class_addMethod(object_getClass(self), sel, walkImp, type); } return [super resolveClassMethod:sel]; }Copy the code

Therefore, the crash is resolved through the dynamic parsing method, so if the method still crashes for some reason, you can try the next step of message forwarding

Redirect forwardingTargetForSelector

If the current class does not have a run method, it will forward the method to the LSStudent object or LSStudent class that has a run method, as shown below

// Redirection of instance methods, Can be redirected to other objects to perform - (id) forwardingTargetForSelector aSelector: (SEL) {if (aSelector = = @ the selector (run)) {/ / forwarded to our LSStudent objects  return [LSStudent new]; } return [super forwardingTargetForSelector:aSelector]; } // Class method redirection, Can be redirected to other classes to perform the + (id) forwardingTargetForSelector aSelector: (SEL) {if (aSelector = = @ the selector (walk)) {/ / forwarded to our LSStudent objects  return [LSStudent class]; } return [super forwardingTargetForSelector:aSelector]; }Copy the code

If LSStudent still does not implement the run or walk methods, there is one final line of defense, method signatures

Message forwarding forwardInvocation

Forward methodSignatureForSelector and forwardInvocation has experienced two method

Take an object method as an example, sign the new method and forward it to other objects that can execute it. The same goes for class methods, so here’s an example of class methods

// Sign the method, Using the new method (NSMethodSignature *) methodSignatureForSelector aSelector: (SEL) {if (aSelector = = @ the selector (run)) {/ / ForwardingTargetForSelector only Method signature without implementation Method Method = class_getInstanceMethod (object_getClass (self), @selector(runReplace)); const char *type = method_getTypeEncoding(method); return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; } return [super methodSignatureForSelector:aSelector]; } // This method can be a subclass of an object. - (void)forwardInvocation:(NSInvocation *)anInvocation {if ([someOtherObject respondsToSelector: [anInvocation selector]]) [anInvocation invokeWithTarget:someOtherObject]; else [super forwardInvocation:anInvocation]; }Copy the code

The method signature mentioned above requires method_getTypeEncoding for a signature, which is the type abbreviation of the method

V Return value void @ indicates object parameters: SEL parameter @ indicates object parametersCopy the code

The type comparison table is as follows

conclusion

The current OC method call can be compiled into a C call study using the clang command

Objc_msgSendSuper calls the specified parent method. Objc_msgSend is a normal method call, also known as send message

Objc_msgSend imp implementation in the cache, if not found, then add to the cache and call the method list, if not found, then continue to find the parent class. Continue caching, search rW’s methods, and eventually, if not found, start dynamic parsing, message forwarding, and redirection related processing