preface

In the previous article, we stated that dynamic method resolution is performed when no method is found in cache_t, giving another chance to call the: resolveInstanceMethod: method, which can be implemented to avoid flashbacks. See objc_msgSend method lookup (middle). If resolveInstanceMethod: is not already implemented, the fast lookup process and the slow lookup process are entered. In this article we need to look at the follow-up process

Find the calling method

Or previous article we said, has been in the lookUpImpOrForward method has been printed in sel name, you’ll find print forwardingTargetForSelector and methodSignatureForSelector, Including forwardingTargetForSelector is quickly find, methodSignatureForSelector is slow. Do we have another way? Let’s introduce a way

Obtaining information from Logs

Write the following code:

@interface Person : NSObject
- (void)likeFood;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc]init];
        [person likeFood];
    }
    return 0;
}
Copy the code

We saw that Person’s likeFood method didn’t work, we called the likeFood method, and we knew that it would crash, so what methods would be called during the crash. inOC Basic principle -objc_msgSend method lookup (middle)In the lookUpImpOrForward method, log_and_fill_cache is called if the done method is used, in this method as followsIn the last article we said that cache_fill writes a called method to cache_t. What is that method? Method below:This method is whether to enable writing to the Log. If so, the logMessageSend method is called as shown in the following figureThis is the write to the log. The write to the log is stored in the/TMP/msgwrite file. The method entry is to make sure objcMsgLogEnabled is true. The default value is falseSo change objcMsgLogEnabled value will need to call instrumentObjcMessageSends method, method below:

We see that if the value passed to flag is inconsistent with the current objcMsgLogEnabled value, flag will be assigned to objcMsgLogEnabled.

Conclusion:

  • 1. ObjcMsgLogEnabled equivalent method to a log switch, instrumentObjcMessageSends method is to control the switch.
  • 2. InstrumentObjcMessageSends inside, outside is don’t know the existence of this method, so we need extern show to the world and the extern we don’t have to tell compile time this method is provided, other places have you to find anywhere else.

Now that we’re done with logging the call method, let’s verify

extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc]init];
        instrumentObjcMessageSends(YES);
        [person likeFood];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}
Copy the code

We go to the TMP file directoryYou can see we don’t have the file we want, run the codeNow we find the generated file, we open the file:We find that the red box is the method we call when we can’t find it.We found resolveInstanceMethod up four times, total forwardingTargetForSelector up 2 times, methodSignatureForSelector up 2 times, DoesNotRecognizeSelector also dropped 2 timesThis is one way to do it, but you need to know where the crash is. Only then can you know where to turn the log switch on and off. Is there another way?

decompiling

We will just write the switch to delete, in the execution of the code, let crash!Print BT, which prints the current stack information. Add: register read We found _ forwarding_prep_0 _. _ forwardingWe can’t find both methods in CoreFoundation by clicking on the stack. Let’s download the source codeDownload address, we use VSCode to open the downloaded source code, search for these two things, found no search, then what to do? We printImage list, which prints all system libraries, we find/System/Library/Frameworks/CoreFoundation framework Versions/A/CoreFoundation, obtaining CoreFoundation, here we use the Hopper for disassemblyThis is the result of disassembling coreFoundation into Hopper, with stack calls coming from the bottom up, so we first search for the ___forwarding_prep_0___ methodWe found that the forwarding method was called in this method. When we clicked on it, we saw a lot of codes, many of which were hard to understand. Let me take a screenshot of the key codes to help us analyze the whole process

If we can’t find a method we call loc_64a2bIf send forwardingTargetForSelector messages, did not respond, will enter the loc_64ad7Checks if it is a zombie object and executes loc_64e31 if it returnsThis method forwardingTargetForSelector is still not implemented, will be executed loc_64e47A warning is reported, and loc_64EAC is calledAn error return error information If forwardingTargetForSelector was carried out, you will perform the following methodDetermine the receiver was rax receiving call forwardingTargetForSelector return values, if rax = = 0 x0, executes loc_64ad7 is the way we said above, it shows that the fast lookup, the receiver must exist, or have an error. When the execution loc_64ad7 forwardingTargetForSelector method (fast lookup) no implementation, then continue to go down, and then the following method is our familiar methodSignatureForSelector: (slowly lookup).See rax = = 0 x0 to illustrate the methodSignatureForSelector: method signature does not implement, will enter loc_64e47 is the process described above. If the method signature is present, the next step is takenThis method has, it is necessary to call to achieve. Keep going downThe _forwardStackInvocation invocation is based on the underlying signature pair, the Target, etc., and the loc_64c89 will be called if the above method is not implementedThe forwardInvocation will then be directed to loc_64f32 if it is empty, which will be saved, and if it is implemented, the next step will be takenThe forwardInvocation is handled and then called directly from the forwardInvocation:, which is why the forwardInvocation is called directly from the outside.

We here by disassembling see forwardingTargetForSelector and methodSignatureForSelector roughly the calling process. We know when to crash, so let’s debug the code.

Code practices to prevent crashes

Validate the quick lookup process

Let’s start with the following code

@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"-->%@", NSStringFromSelector(aSelector));
    return [super forwardingTargetForSelector:aSelector];
}
@end
Copy the code

LikeFood method unrealized, we run found collapsed, but the print go forwardingTargetForSelector likeFood.ForwardingTargetForSelector (official explanation the meaning of this method: this method to the unrealized method to look for a receiver), let’s create Man class, the following code:

@interface Man : NSObject
- (void)likeFood;
@end

@implementation Man
- (void)likeFood {
    NSLog(@"%s",__func__);
}
@end

@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"-->%@", NSStringFromSelector(aSelector));
    return [Man alloc];
}
@end
Copy the code

Run code:Found that it didn’t crashAt this point we need Man method and Person, because forwardingTargetForSelector tells the compiler to Man is to find the method implementation.

Verify slow lookup

Above is the check-up quickly find forwardingTargetForSelector, we verify methodSignatureForSelector below. Prepare the following code

@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"-->%@", NSStringFromSelector(aSelector));
    return [super forwardingTargetForSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s-%@", __func__, NSStringFromSelector(aSelector));
    return [super methodSignatureForSelector:aSelector];
}
@end
Copy the code

Run code:Not be implemented in forwardingTargetForSelector methodSignatureForSelector methods performed when we in the disassembly analysis (verified). The following changes are made to the code

@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"-->%@", NSStringFromSelector(aSelector));
    return [super forwardingTargetForSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"--%s-%@", __func__, NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"--=%s--%@", __func__, anInvocation);
}
@end
Copy the code

Let’s run it againIt won’t crash, let’s see what’s inside the NSInvocationSel and target were foundLet’s print the NSInvocation found insideIt contains the receiver information, it contains the method that was called, and this is the slow lookup, it packs up all the information about that method, and it throws it out, and it doesn't care who can handle it. We’re working on the code

@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"-->%@", NSStringFromSelector(aSelector));
    return [super forwardingTargetForSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"--%s-%@", __func__, NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"--=%s--%@", __func__, anInvocation);
    anInvocation.target = [Man alloc];
    anInvocation.selector = NSSelectorFromString(@"likeRun");
    [anInvocation invoke];
}
@end
Copy the code

Run it again and print the following:It means that a slow search is akin to packing up all the information about a method, throwing it away, and assigning it a receiver or method to whoever gets it.

conclusion

Above we talked about fast lookup and slow lookup processes, how to know what method to call, we used print log, disassemble to get,The method of capturing the process is important. We experimented with fast and slow lookup methods to prevent crashes. Finally, let’s make up another flow chart

Supplementary – Class method dynamic method resolution

In the previous article, we talked about dynamic method resolution for object methods. Let’s write the following code

@interface Person : NSObject
+ (void)likeColor;
@end

@implementation Person
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [Person likeColor];
    }
    return 0;
}
Copy the code

Our Person class method was not implemented and called. Interrupt point run, front and object method until we find this methodIf the current class method comes in, then the inst is the current recipient’s class, and then the Person, the CLS is the metaclass, the metaclass of PersonCLS ->isMetaClass() ->isMetaClass() ->isMetaClass(The resolveClassMethod method is called, and the resolveClassMethod method is similar to the object method. According to object method guidance:If we implement resolveClassMethod we can also prevent crashes. Try it and prepare the following code in Person.m

@implementation Person + (BOOL)resolveClassMethod:(SEL) SEL {NSLog(@"%@ ",NSStringFromSelector(SEL)); if (sel == @selector(likeColor)) { IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(workTime)); Method likeMMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(workTime)); const char *type = method_getTypeEncoding(likeMMethod); return class_addMethod(objc_getMetaClass("Person"), sel, imp, type); } return [super resolveClassMethod:sel]; } + (NSString *)workTime { NSLog(@"%s", __func__); return @"09:00"; } @endCopy the code

Run theFinding no crash error indicates that the explanation is correct. ResolveInstanceMethod () : resolveInstanceMethod () : resolveClassMethod ();

If this method is a class method call, it should give the class method a chance to query again, to save, save the place should be metaclass. The methods of a class are in the form of object methods, and the methods of a metaclass are in the form of class-object methods. We call resolveInstanceMethod, and then call lookUpImpOrForward in the metaclass and its superclass. But resolveInstanceMethod is an object method, and it is only possible to find the resolveInstanceMethod method in the parent class of the NSObject metaclass.

The above analysis tells us that implementing resolveInstanceMethod in NSObject can also prevent class methods from crashing. We create the NSObject class because the resolveInstanceMethod method is already implemented in NSObject, and we create the class to override the resolveInstanceMethod.

@implementation NSObject (Test) + (BOOL)resolveInstanceMethod: SEL SEL {NSLog(@"%@ ",NSStringFromSelector(SEL)); If (sel == @selector(likeFood)) {NSLog(@"%@ ",NSStringFromSelector(sel)); IMP imp = class_getMethodImplementation(self, @selector(sleepTime)); Method sayMMethod = class_getInstanceMethod(self, @selector(sleepTime)); const char *type = method_getTypeEncoding(sayMMethod); return class_addMethod(self, sel, imp, type); }else if (sel == @selector(likeColor)) { IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(workTime)); Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(workTime)); const char *type = method_getTypeEncoding(sayMMethod); return class_addMethod(objc_getMetaClass("Person"), sel, imp, type); } return YES; } @endCopy the code

Run, perfect solutionBut this writing method can be cut without interception without implementation method, can be optimizedFor example, the method created by myself starts with PR, the home page is pr_Home_method, my method is pr_Me_methods, and the message is pr_IM_methods. We intercept unimplemented methods here. If we find that the method starts with PR, we intercept it, if not, we pass it. If it is Home, it is the Home page method, and so on. Methods in different places do different things, but this is not good, it is best to do in the lowest level of slow search process.

Wrote last

LookUpImpOrForward is an important method for sending messages. Actual breakpoints found that this method can multiple calls when can’t find the imp, came the sel is endless and same, follow-up studies clearly add, resolveInstanceMethod call many times, also has relationship with this

Add the content

ResolveInstanceMethod is called twice and lookUpImpOrForward is called multiple times Red box 1 is to look for the resolveInstanceMethod method, which I'm sure you'll find because it's implemented in NSObject, and it goes to red box 2 because it found the method, so call resolveInstanceMethod and see if it handles sel. This place is the first call resolveInstanceMethod, call the red box 3 will directly return below imp address is nil, the second call is after methodSignatureForSelectorLet’s interrupt down hereRun to print the stack every time you hit a breakpoint. The following is to prove what we said