As you learned in the previous chapter, OC calls the methods, and the underlying call is objc_msgSend to send a message. When sending a message will go through a series of fast search, slow search, if the search to the corresponding IMP, directly return; If it does not, it enters the dynamic parsing and message forwarding process of the method.

1. Dynamic method resolution

By exploring objc_msgSend source code, when the slow search still does not find IMP, will enter the method dynamic parsing stage, source code is as follows:

_class_resolveMethod
retry
_class_resolveMethod

_class_resolveMethod

Void _class_resolveMethod(Class CLS, SEL SEL, id inst) {// Whether it is a metaclassif (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]

        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else{ // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] _class_resolveClassMethod(cls, sel, inst); // It has been processedif(! lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/) {// object method resolver _class_resolveInstanceMethod(CLS, sel, inst); }}}Copy the code

Because class methods are stored in metaclasses, they are handled differently in _class_resolveMethod

Metaclass: indicates that the class methods in the metaclass are processed, but the methods in the metaclass are stored in the form of instance methods in the root metaclass. Therefore, the instance method of the root metaclass is eventually searched, and the instance method is called to parse the search. Non-metaclasses: Handle instance methods stored in a class.Copy the code

1.1 Instance method dynamically resolves _class_resolveInstanceMethod

Dynamic parsing of instance methods in _class_resolveInstanceMethod method, source code is as follows:

static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) { // 1. Check whether the system implements SEL_resolveInstanceMethod // that is +(BOOL)resolveInstanceMethod:(SEL) SEL, // inherited from NSObject class, default implementation, return NOif(! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {// Resolver not implemented. There is no need for dynamic parsingreturn; BOOL (* MSG)(Class, sel, sel) = (typeof(MSG))objc_msgSend; bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); IMP IMP = lookUpImpOrNil(CLS, sel, inst, NO/*initialize*, YES/*cache*/, NO/*resolver*/);if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : The '-', 
                         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)); }}}Copy the code

+(BOOL)resolveInstanceMethod (SEL) SEL method in the unimplemented method specified IMP, and add to the class, implement method dynamic resolution, prevent system crash.

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    NSLog(@"Here we go, buddy :%s - %@",__func__,NSStringFromSelector(sel));

    if (sel == @selector(saySomething)) {
        NSLog(@"Speak.");
        IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
        Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
        const char *sayHType = method_getTypeEncoding(sayHMethod);
        return class_addMethod(self, sel, sayHIMP, sayHType);
        }
    
    return [super resolveInstanceMethod:sel];
}
Copy the code

We can also collect bugs in this method based on the prefix, route, transaction, jump to different pages.

1.2 _class_resolveClassMethod

If it is a metaclass, the related class method is dynamically resolved in the _class_resolveClassMethod method. The implementation steps of this method are similar to the implementation steps of the instance method, except that the metaclass is obtained when the message is sent, that is:

_class_resolveClassMethod

static void _class_resolveClassMethod(Class cls, SEL sel, id inst) { assert(cls->isMetaClass()); SEL_resolveClassMethod :(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethodif (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return; +(BOOL)resolveClassMethod:(SEL) SEL method BOOL (* MSG)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(_class_getNonMetaClass(cls, inst), SEL_resolveClassMethod, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls //3. IMP IMP = lookUpImpOrNil(CLS, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); 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() ? '+' : The '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code

+ (BOOL)resolveClassMethod (SEL) SEL

+ (BOOL)resolveClassMethod:(SEL)sel{
    
    NSLog(@"Here comes class method :%s - %@",__func__,NSStringFromSelector(sel));

     if (sel == @selector(sayLove)) {
         NSLog(@"Say it. - Say you you love me."); IMP sayHIMP = class_getMethodImplementation(self, @selector(sayObjc)); Method sayHMethod = class_getClassMethod(self, @selector(sayObjc)); const char *sayHType = method_getTypeEncoding(sayHMethod); // Class methods in the metaclass objc_getMetaClass("LGStudent")
         return class_addMethod(self, sel, sayHIMP, sayHType);
     }
     return [super resolveClassMethod:sel];
}
Copy the code

summary

  1. Methods first go through the cache search, method list search, and then enter the dynamic method parsing stage
  2. Instance method resolution needs to be implementedresolveInstanceMethodmethods
  3. Class method resolution needs to be implementedresolveClassMethodmethods
  4. Class methods are stored in metaclasses becauseThe metaclassInherited fromA metaclass.A metaclassUltimately inherited fromNSObjectSo when you parse a class method, you’ll eventually find itNSObject. Because the metaclass and root class are created by the system and cannot be modified, you can recreate the parent class of the root metaclassNSObjectIn, add the corresponding instance methodresolveInstanceMethodDynamic parsing.

2. Forwarding messages

In the process of method search, after cache search, method list search and dynamic method analysis, if the above steps are not found to IMP, there is no method dynamic analysis, then it will enter the last step, crash.

_objc_msgForward_impcache

STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band Z is  0 (EQ)for normal, 1 (NE) for stret

	beq	__objc_msgForward
	b	__objc_msgForward_stret
	
	END_ENTRY __objc_msgForward_impcache
	

	ENTRY __objc_msgForward
	// Non-stret version

	MI_GET_EXTERN(r12, __objc_forward_handler)
	ldr	r12, [r12]
	bx	r12

	END_ENTRY __objc_msgForward


	ENTRY __objc_msgForward_stret
Copy the code

In _objc_msgForward_impcache, call __objc_msgForward, then call __objc_forward_handler, override the _objc_forward_handler OC method as follows, and then a classic crash.

So why is a stack like the one above printed during a crash?

lookUpImpOrForward
IMP
log_and_fill_cache

log_and_fill_cache
objcMsgLogEnabled
/tmp/msgSends
objcMsgLogEnabled
instrumentObjcMessageSends

Check the file in the/TMP/msgtimeline directory, as shown below:

resolveInstanceMethod:
forwardingTargetForSelector
methodSignatureForSelector
doesNotRecognizeSelector

2.1 Fast forwarding process

By looking at the forwardingTargetForSelector official documentation,

- (id)forwardingTargetForSelector:(SEL)aSelector

1. Return an object. If the object is non-empty and non-nil, the system forwards the message to the object for execution, otherwise, it continues to look for other processes. The system gives the SEL the opportunity to be passed to other objects. 2. If nil is returned or message forwarding is not processed, the forwardInvocation: method is used to process the message forwarding process.Copy the code

You can use the following code to forward the message of the saySomething method to the LGTeacher class without causing a system crash, so that the message is quickly forwarded.

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) {
        return [LGTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}
Copy the code

2.2 Slow Forwarding Process

Into slow search process, you must first achieve methodSignatureForSelector method, return to a signature, this method signature encapsulates the return value type, the parameter type and other information.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) { // v @ :
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
Copy the code

Then you must also implement – (void)forwardInvocation:(NSInvocation *)anInvocation;

- (void)forwardInvocation:(NSInvocation *)invocation
{
    SEL aSelector = [invocation selector];
 
    if ([friend respondsToSelector:aSelector])
        [invocation invokeWithTarget:friend];
    else
        [super forwardInvocation:invocation];
}
Copy the code

Note:

1. ForwardInvocation method and methodSignatureForSelector method must be implemented at the same time 2. MethodSignatureForSelector generates a signature, NSInvocation object, 3. Pass the NSInvocation object as a parameter to the forwardInvocation method. The forwardInvocation method gives the message to the object that can handle the message to avoid a crash when the object calls the didNotRecognizeSelector method. 4. The forwardInvocation method is similar to the message being stored as a transaction. Here who can operate in this inside the operation, even if not the operation will not crash, here is the last processing opportunity to prevent crash.Copy the code

Let’s look at the forwardInvocation implementation in NSObject:

+ (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}

Copy the code

This shows that the system does not throw an exception in the doesnot need cognizeselector method, so if the forwardInvocation method is overwritten, it will not crash regardless of whether there is an implementation in it, or if the parent method is invoked.

Message forwarding flowchart:

conclusion

When an unimplemented method is called, there are three solutions:

1, resolveInstanceMethod: object to send the message to add an IMP, 2, and then let the object handle forwardingTargetForSelector: Will forward the message to can handle the message object 3, methodSignatureForSelector and forwardInvocation: The first method generates the method signature, then creates the NSInvocation object as a parameter to the second method, then does the message processing in the forwardInvocation method, so long as the parent method is not executed in the second method, it will not crashCopy the code

What about the resolveInstanceMethod method being called twice?

  1. When looking forIMPIs called for the first time when the method dynamic parsing is entered if it is not foundresolveInstanceMethod
  2. Then enter the message forwarding process, callforwardingTargetForSelector, forwards the message to an object that can process the message
  3. callmethodSignatureForSelectorandforwardInvocation, return signature
  4. callforwardInvocation

resolveInstanceMethod
class_getInstanceMethod
resolveInstanceMethod