Calling a method in Objective-C actually calls the objc_msgSend() function.
- This function does a quick lookup first, that is, from the method cache, and this quick lookup is implemented directly by assembly;
- If there is no cache, a slow search process is entered
lookUpImpOrForward()
, from the class’s list of methodsobjc_class->bit.data()(rw)->ro->baseMethodList
At the same time, the called method will be usedThe Hash table
The structure is preserved inobjc_class->cache
In, we call it slow search; - If not found in step 2, continue down the inheritance chain from the parent class, again looking for the cache, then for the method list;
- If an IMP is found in any of the above steps, it is cached (into the current class). If there is no IMP in the end, the message forwarding process enters.
I. Dynamic method analysis:resolveMethod
When the IMP corresponding to SEL is not found, The system calls +(BOOL)resolveInstanceMethod:(SEL) SEL or +(BOOL)resolveClassMethod:(SEL) SEL to allow us to specify an IMP for SEL.
#include <objc/runtime.h>
@implementation Dog
//- (void)walk {
// NSLog(@"walk");
//}
- (void)run {
NSLog(@"run");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(walk)) {
NSLog(@"Call walk");
Method method = class_getInstanceMethod(self, @selector(run));
IMP imp = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
class_addMethod(self, sel, imp, types);
return YES;
}
return [super resolveInstanceMethod:sel];
}
Copy the code
Run [Dog Walk], console output
2020-04-02 16:28:54.656555+0800 tset[88242:1804683] Call walk
2020-04-02 16:28:54.657590+0800 tset[88242:1804683] run
Copy the code
If the above two methods are not processed, it goes below.
Second, fast forwarding of messages
– (id) forwardingTargetForSelector aSelector: (SEL), we can in this method to specify a target to handle aSelector
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"will forward msg: %s", (char *)aSelector);
Cat *cat = [[Cat alloc] init];
if ([cat respondsToSelector:aSelector]) {
NSLog(@"forwards msg %s success!", (char *)aSelector);
return cat;
}
return [super forwardingTargetForSelector:aSelector];
}
Copy the code
Run [Dog Walk], console output
2020-04-02 16:55:16.636043+0800 tset[89075:1816360] will forward msg: walk
2020-04-02 16:55:16.636884+0800 tset[89075:1816360] forwards msg walk success!
2020-04-02 16:55:16.637107+0800 tset[89075:1816360] -[Cat walk]
Copy the code
Slow forwarding of messages
If we do not implement the above method, the new method is called.
1. Method signature
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"method signature for msg: %s", (char *)aSelector);
if (aSelector == @selector(anwser)) {
NSLog(@"method signature for msg: %s susscess!", (char *)aSelector);
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
Copy the code
2. Transaction forwarding
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation: %s", (char *)anInvocation.selector);
SEL selector = [anInvocation selector];
Cat *cat = [[Cat alloc] init];
if ([cat respondsToSelector:selector]) {
NSLog(@"give invacation a target");
[anInvocation invokeWithTarget:cat];
} else{ [super forwardInvocation:anInvocation]; }}Copy the code
Four,
When the IMP is not found after the method search process ends, runtime first performs dynamic method parsing, then enters fast message forwarding, and finally slow message forwarding:
- Dynamic method resolution: call +resolveInstanceMethod or +resolveClassMethod to try to get IMP
- No IMP, fast forward, call – forwardingTargetForSelector: try to get a can handle object
- Still no processing, into the slowly forward, call – methodSignatureForSelector: access to the method signature, after the message is encapsulated to call for an invocation – forwardInvocation: for processing. As you can see, when a method is not implemented, the Runtime gives us three opportunities to process it. Here is the flow of dynamic method resolution and message forwarding:
Five, the source
Attached below is the source code for lookUpImpOrForward in objC4-750, which is in the objC-Runtime-new.mm class. This method realizes the whole process of slow search to find dynamic forwarding.
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead. **********************************************************************/ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = nil; bool triedResolver = NO; runtimeLock.assertUnlocked(); // Optimistic cache lookup if (cache) { imp = cache_getImp(cls, sel); if (imp) return imp; } // runtimeLock is held during isRealized and isInitialized checking // to prevent races against concurrent realization. // runtimeLock is held during method search to make // method-lookup + cache-fill atomic with respect to method addition. // Otherwise, a category could be added but ignored indefinitely because // the cache was re-filled with the old value after the cache flush on // behalf of the category. runtimeLock.lock(); checkIsKnownClass(cls); if (! cls->isRealized()) { realizeClass(cls); } if (initialize && ! cls->isInitialized()) { runtimeLock.unlock(); _class_initialize (_class_getNonMetaClass(cls, inst)); runtimeLock.lock(); // If sel == initialize, _class_initialize will send +initialize and // then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called // from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertLocked();
// Try this class's cache. imp = cache_getImp(cls, sel); if (imp) goto done; // Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for(Class curClass = cls->superclass; curClass ! = nil; curClass = curClass->superclass) { // Haltif there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if(imp ! = (IMP)_objc_msgForward_impcache) { // Found the methodin a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method // resolver for this class first. break; } } // Superclass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; } } } // No implementation found. Try method resolver once. if (resolver && ! triedResolver) { runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); runtimeLock.lock(); // Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead. triedResolver = YES; goto retry; } // No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
Copy the code