Dynamic method parsing

If the message sending phase is not successful, then the dynamic method resolution phase is entered

The function resolveMethod_locked is called differently for class objects and metaclass objects

static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); // Not a metaclass object. cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] resolveInstanceMethod(inst, sel, cls); } else {/ / is a metaclass object / / try [nonMetaClass resolveClassMethod: sel] / / and/CLS resolveInstanceMethod: sel resolveClassMethod(inst, sel, cls); If (!) {if (!) {if (!) {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

Note: if you can call this, it means that you have found the metaclass object of the base class. If there is still no object method of the same name, then you will look for the object method of the base class

[Step 2] If it is a class object, it will enter the resolveInstanceMethod function and go through the message sending process to find whether the resolveInstanceMethod method is implemented. If it is not implemented, it will return. If there is an implementation, the resolveInstanceMethod method is called and the message is sent to see if there is an implementation instance method

static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); ResolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod if (! lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) { // Resolver not implemented. return; } // resolveInstanceMethod is already implemented, ResolveInstanceMethod 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); ResolveInstanceMethod: RESOLVE resolveInstanceMethod: resolveResoltresolving {if (resolved && PrintResolving) {if (imp) {_objc_inform("RESOLVE: resolveInstanceMethod: resolveResoltresolving) {if (resolved && PrintResolving) {if (imp) {_objc_inform("RESOLVE: resolveInstanceMethod: resolveResoltresolving) { 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)); }}}Copy the code

The following isresolveInstanceMethodSome of the detailed call parsing

1. If lookUpImpOrNilTryCache is called several times, _lookUpImpTryCache is called again internally

IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
Copy the code

Lookupimptrycache: lookUpImpOrForward: lookUpImpOrForward: lookUpImpOrForward: LookupImptryCache: lookUpImpOrForward: lookUpImpOrForward: LookupImptryCache: lookUpImpOrForward

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

[Step 2] If it is a metaclass object, it will enter the resolveClassMethod function, and the dynamic method resolution of the same object is roughly similar. The resolveClassMethod method will be returned if it is not implemented. Send a message call if there is an implementation

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    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() a.k.a. cls
    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

To verify the source implementation, let’s create sample code first

1. Create a Person class, add the test object method, implement the resolveInstanceMethod method, and dynamically add the other function

@interface Person : NSObject - (void)test; @end @implementation Person - (void)other { NSLog(@"%s", __func__); } + (BOOL)resolveInstanceMethod:(SEL) SEL {if (SEL == @selector(test)) {// get other methods class_getInstanceMethod(self, @selector(other)); // Dynamically add an implementation of the test method class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method)); Return YES; } return [super resolveInstanceMethod:sel]; } @endCopy the code

(struct method_t *) (struct method_t *

2. If you call [Person test] from the main function, the console will print that the other function has been called

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        [person test];
    }
    return 0;
}
Copy the code
conclusion

As you can see from the sample code, use the Message sending mechanism of the Runtime to dynamically increase the implementation and invocation of some methods

The entire flow of dynamic method analysis can be expressed in the following figure

forward

If there is no code implementation for dynamic analysis, you enter the message forwarding phase

The source code of message forwarding is not open source, so we can only analyze its internal implementation through some other methods

Implementation steps

1. First of all, we check which methods are called in message forwarding by printing the log of method crash

We commented out the resolveInstanceMethod implementation in the Person.m file, ran the program again, and found that the program crashed and printed a classic error message

-[Person test]: unrecognized selector sent to instance 0x1018331d0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person test]: unrecognized selector sent to instance 0x1018331d0'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff204a16af __exceptionPreprocess + 242
	1   libobjc.A.dylib                     0x00007fff201d93c9 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff20523c85 -[NSObject(NSObject) __retain_OA] + 0
	3   CoreFoundation                      0x00007fff2040906d ___forwarding___ + 1467
	4   CoreFoundation                      0x00007fff20408a28 _CF_forwarding_prep_0 + 120
	
	6   libdyld.dylib                       0x00007fff2034a631 start + 1
	7   ???                                 0x0000000000000001 0x0 + 1
)
Copy the code

From the call stack print above, we can see that the system calls ___forwarding___ from the CoreFoundation framework first

2. We can disassemble the CoreFoundation framework using the reverse Hopper tool. Through a series of operations, we can get the pseudo-code __forwarding_prep_0___

Source code analysis

The following is an implementation of the pseudocode

Void *frameStackPointer, int isStret) {id receiver = *(id *)frameStackPointer; SEL sel = *(SEL *)(frameStackPointer + 8); const char *selName = sel_getName(sel); Class receiverClass = object_getClass(receiver); / / call forwardingTargetForSelector: if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) { id forwardingTarget = [receiver forwardingTargetForSelector:sel]; if (forwardingTarget && forwardingTarget ! = receiver) { if (isStret == 1) { int ret; objc_msgSend_stret(&ret,forwardingTarget, sel, ...) ; return ret; } return objc_msgSend(forwardingTarget, sel, ...) ; }} // Zombie object const char *className = class_getName(receiverClass); const char *zombiePrefix = "_NSZombie_"; size_t prefixLen = strlen(zombiePrefix); // 0xa if (strncmp(className, zombiePrefix, prefixLen) == 0) { CFLog(kCFLogLevelError, @"*** -[%s %s]: message sent to deallocated instance %p", className + prefixLen, selName, receiver); < breakpoint interrupt - >} / / call methodSignatureForSelector call forwardInvocation if again after obtain the method signature (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) { NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel]; if (methodSignature) { BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct; if (signatureIsStret ! = isStret) { CFLog(kCFLogLevelWarning , @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.", selName, signatureIsStret ? "" : not, isStret ? "" : not); } if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) { NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer]; [receiver forwardInvocation:invocation]; void *returnValue = NULL; [invocation getReturnValue:&value]; return returnValue; } else { CFLog(kCFLogLevelWarning , @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message", receiver, className); return 0; } } } SEL *registeredSel = sel_getUid(selName); // Selector is already registered with the Runtime if (sel! = registeredSel) { CFLog(kCFLogLevelWarning , @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", sel, selName, registeredSel); } // doesNotRecognizeSelector else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) { [receiver doesNotRecognizeSelector:sel]; } else { CFLog(kCFLogLevelWarning , @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort", receiver, className); } // The point of no return. kill(getpid(), 9); }Copy the code

1. First of all, to check whether implementation forwardingTargetForSelector method, if the implementation the method and the return value is nil, then invokes the message flow objc_msgSend; If this method is not implemented, or the return value is nil, it invokes the method signature methodSignatureForSelector

2. If you don’t is nil methodSignatureForSelector return values, it can call forwardInvocation, if this method is not implemented or the return value is nil, then is called doesNotRecognizeSelector, DoesNotRecognizeSelector there will be a crash error

3. If the forwardInvocation is not implemented, a crash is also reported

4. To verify the above source code analysis, add a Cat class and implement the test method

@interface Cat: NSObject

- (void)test;
@end

@implementation Cat

- (void)test {
    NSLog(@"%s", __func__);
}
@end
Copy the code

Implement these three functions in the person. mm file, return nil or comment out the implementation, and run the program

@implementation Person //+ (BOOL)resolveInstanceMethod:(SEL)sel //{ // class_addMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>) //} - (id)forwardingTargetForSelector:(SEL)aSelector { NSLog(@"%s", __func__); if (aSelector == @selector(test)) { // objc_msgSend([[Cat alloc] init], aSelector) return nil; //[[Cat alloc] init]; //[[NSObject alloc] init]; } return [super forwardingTargetForSelector:aSelector]; } / / the method signature: return value type and parameter types - (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {NSLog (@ "% s", __func__); if (aSelector == @selector(test)) { return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"]; } return [super methodSignatureForSelector:aSelector]; } // NSInvocation encapsulates a method call including: // Invocation invocation. Target Invocation. Selector Method name // [anInvocation Invocation :NULL atIndex:0] - (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@"%s", __func__); // anInvocation.target = [[Cat alloc] init]; // [anInvocation invoke]; [anInvocation invokeWithTarget:[[Cat alloc] init]]; // [anInvocation invokeWithTarget:[[NSObject alloc] init]]; } @endCopy the code

The method signature methodSignatureForSelector return value of a variety of writing

[NSMethodSignature signatureWithObjCTypes:" I @: I "] [[Cat alloc] init] methodSignatureForSelector:aSelector];Copy the code

The forwardInvocation gets the caller, method name, and method parameters for the entire method from the NSInvocation argument

// Change the caller object [anInvocation invokeWithTarget:[Cat alloc] init]]; // Get the parameter information, pass the address value int age; [anInvocation getArgument:&age atIndex:2]; Int ret; [anInvocation getReturnValue:&ret];Copy the code

Note:

  • willforwardingTargetForSelectorThe return value of theNSObject object, discovery will also crash error; If the type of the returned value cannot be found, the message will be sent again and finally crash with an error
  • forwardInvocationImplementation can do anything, as long as the implementation of the function, will not crash error; The premise is not to do unimplemented methodsinvokeWithTargetThe object
  • The above methods have object method, class method two versions
conclusion

The whole process of message forwarding analysis can be described in the following figure

The interview questions

1. Who does self and super correspond to in the following code

@interface Person : NSObject

- (void)run;
@end

@implementation Person

- (void)run {
    NSLog(@"%s", __func__);
}
@end

@interface Student: Person

@end

@implementation Student

- (instancetype)init {

    if (self = [super init]) {
        NSLog(@"[self class] = %@", [self class]); // Student
        NSLog(@"[self superclass] = %@", [self superclass]); // Person

        NSLog(@"[super class] = %@", [super class]); // Student
        NSLog(@"[super superclass] = %@", [super superclass]); // Person
    }
    return self;
}

- (void)run {

    [super run];
}
@end
Copy the code

When you convert this code into a C++ file, you can see that the underlying run method calls the objc_msgSendSuper function

static void _I_MJStudent_run(MJStudent * self, SEL _cmd) {
//    objc_msgSendSuper(__rw_objc_super { self, [Person class]) },
//                      sel_registerName("run")
//                      );
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("run"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_MJStudent_69ea3c_mi_0);
}
Copy the code

We also find that one of the arguments is a struct type of __rw_objc_super, which has two members facing self and class_getSuperclass(objc_getClass(“Student”)), the Student object and the parent Person object

struct __rw_objc_super { 
	struct objc_object *object; 
	struct objc_object *superClass; 
	__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 
};
Copy the code

The implementation of the objc_msgSendSuper function can be found in objC-msG-arm64.s file in the objC source code

ENTRY _objc_msgSendSuper
	UNWIND _objc_msgSendSuper, NoFrame

	ldp	p0, p16, [x0]		// p0 = real receiver, p16 = class
	b L_objc_msgSendSuper2_body

	END_ENTRY _objc_msgSendSuper

	// no _objc_msgLookupSuper

	ENTRY _objc_msgSendSuper2
	UNWIND _objc_msgSendSuper2, NoFrame

#if __has_feature(ptrauth_calls)
	ldp	x0, x17, [x0]		// x0 = real receiver, x17 = class
	add	x17, x17, #SUPERCLASS	// x17 = &class->superclass
	ldr	x16, [x17]		// x16 = class->superclass
	AuthISASuper x16, x17, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS
LMsgSendSuperResume:
#else
	ldp	p0, p16, [x0]		// p0 = real receiver, p16 = class
	ldr	p16, [x16, #SUPERCLASS]	// p16 = class->superclass
#endif
L_objc_msgSendSuper2_body:
	CacheLookup NORMAL, _objc_msgSendSuper2, __objc_msgSend_uncached

	END_ENTRY _objc_msgSendSuper2	
Copy the code

_objc_msgSendSuper2 will be called based on the superclass pointer of the current type to find the superclass method. The real type of the first structure variable passed in is objc_super2

struct objc_super2 {
    id receiver;
    Class current_class;
};
Copy the code

We can see the implementation of the class method in objC source nsobject. mm, which gets the type of the current caller. The passed self is the current caller, so either [self class] or [super class] gets the type of the current caller. Student type

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}
Copy the code

The implementation of the superclass method is to get the parent type of the current caller’s type, so either self superclass or super superclass gets the Person type

+ (Class)superclass {
    return self->getSuperclass();
}

- (Class)superclass {
    return [self class]->getSuperclass();
}
Copy the code

2. Can the following code be executed successfully? If so, what is the print result?

@interface Person : NSObject @property (copy, nonatomic) NSString *name; - (void)print; @end @implementation Person - (void)print { NSLog(@"my name is %@", self->_name); My name is <ViewController: 0x7fcfc2a04720> } @end @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; id cls = [Person class]; void *obj = &cls; [(__bridge id)obj print]; } @endCopy the code

Create a Person object Person * Person = [[Person alloc] init] is the same as obj’s [Person class], so obj can call print directly

Then we know that the memory addresses of several local variables in viewDidLoad are arranged from largest to smallest, so they can be represented in the following figure

The essence of [super viewDidLoad] is to call objc_msgSendSuper2, whose first argument is a struct variable similar to the following

struct objc_super2 {
   self,
   [ViewController class]
};
Copy the code

So the distribution in memory is shown in the figure below

The essence of the Person structure is shown in the following code, where the member variables are arranged in ascending order; So self->_name means skipping the ISA pointer in the structure to find the _name member variable, which is equivalent to obj skipping CLS to find self in the structure variable, and then getting the value is the memory address of the ViewController

struct Person_IMPL
{
   Class isa;
   NSString *_name;
};
Copy the code

3. Specific application of Runtime

Use AssociatedObject to add attributes to the class to traverse all the member variables of the class (modify textField placeholder color, dictionary transform model, automatic archive unfile) exchange method implementation (exchange system method, Listen for multiple button clicks) resolve exceptions where methods cannot be found using message forwarding mechanisms

1. The replacement class

@interface Person: NSObject - (void)run; @end @implementation Person - (void)run { NSLog(@"%s", __func__); } @end @interface Car: NSObject - (void)run; @end @implementation Car - (void)run { NSLog(@"%s", __func__); } @end Person *person = [[Person alloc] init]; [person run]; object_setClass(person, [Car class]); [person run]; // Print [Person run], [Car run]Copy the code

2. Check whether it is a class object

NSLog(@"%d %d %d",
          object_isClass(person),
          object_isClass([Person class]),
          object_isClass(object_getClass([Person class]))
          );
Copy the code

3. Dynamically create classes

void run(id self, SEL _cmd) { NSLog(@"_____ %@ - %@", self, NSStringFromSelector(_cmd)); } // create Class Class newClass = objc_allocateClassPair([NSObject Class], "Dog", 0); class_addIvar(newClass, "_age", 4, 1, @encode(int)); class_addIvar(newClass, "_weight", 4, 1, @encode(int)); class_addMethod(newClass, @selector(run), (IMP)run, "v@:"); // Register class objc_registerClassPair(newClass); id dog = [[newClass alloc] init]; [dog setValue:@10 forKey:@"_age"]; [dog setValue:@20 forKey:@"_weight"]; [dog run]; // dispoSeclasspair (newClass);Copy the code

Note: Since member variables are read-only attributes, they must be added before the class is registered; For the sake of specification, it is best to add attributes, member variables, and methods before registering a class

4. Member variable related usage

Ivar ageIvar = class_getInstanceVariable([Person class], "_age"); NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar)); Ivar nameIvar = class_getInstanceVariable([Person class], "_name"); Person *person = [[Person alloc] init]; object_setIvar(person, nameIvar, @"123"); Object_setIvar (person, ageIvar, (__bridge id)(void *)10); NSLog(@"%@ %d", person.name, person.age); // The number of member variables unsigned int count; Ivar *ivars = class_copyIvarList([Person class], &count); for (int i = 0; i < count; Ivar = ivars[I]; Ivar = ivars[I]; NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar)); } free(ivars);Copy the code

5. Substitution of method implementation

void myrun()
{
    NSLog(@"---myrun");
}

Person *person = [[Person alloc] init];
    
class_replaceMethod([Person class], @selector(run), (IMP)myrun, "v");
    
// class_replaceMethod([Person class], @selector(run), imp_implementationWithBlock(^{
//     NSLog(@"123123");
// }), "v");
    
[person run];
Copy the code

Swap the two methods in the class

@implementation Person

- (void)run
{
    NSLog(@"%s", __func__);
}

- (void)test
{
    NSLog(@"%s", __func__);
}

@end

Person *person = [[Person alloc] init];
        
Method runMethod = class_getInstanceMethod([Person class], @selector(run));
Method testMethod = class_getInstanceMethod([Person class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);

[person run];
Copy the code

Method interchange is used for fault tolerance

@interface NSMutableArray (Extension) @end @implementation NSMutableArray (Extension) + (void)load { static dispatch_once_t onceToken; Dispatch_once (&oncetoken, ^{ Class CLS = NSClassFromString(@"__NSArrayM"); Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:)); Method method2 = class_getInstanceMethod(cls, @selector(ll_insertObject:atIndex:)); method_exchangeImplementations(method1, method2); }); } - (void)ll_insertObject:(id)anObject atIndex:(NSUInteger)index { if (anObject == nil) return; [self ll_insertObject:anObject atIndex:index]; } @endCopy the code

We can see the implementation of this method in objC4 source code, which is to swap the IMP of the two functions and clear the cache

void method_exchangeImplementations(Method m1, Method m2) { if (! m1 || ! m2) return; mutex_locker_t lock(runtimeLock); IMP imp1 = m1->imp(false); IMP imp2 = m2->imp(false); SEL sel1 = m1->name(); SEL sel2 = m2->name(); m1->setImp(imp2); m2->setImp(imp1); // RR/AWZ updates are slow because class is unknown // Cache updates are slow because class is unknown // fixme build list of classes whose Methods are known externally? flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){ return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2); }); adjustCustomFlagsForMethodChange(nil, m1); adjustCustomFlagsForMethodChange(nil, m2); }Copy the code

Clear the cached data in this function

static void flushCaches(Class cls, const char *func, bool (^predicate)(Class)) { runtimeLock.assertLocked(); #if CONFIG_USE_CACHE_LOCK mutex_locker_t lock(cacheUpdateLock); #endif const auto handler = ^(Class c) {if (predicate(c)) {// Empty data c-> cache.erasenolock (func); } return true; }; if (cls) { foreach_realized_class_and_subclass(cls, handler); } else { foreach_realized_class_and_metaclass(handler); }}Copy the code