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 isresolveInstanceMethod
Some 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:
- will
forwardingTargetForSelector
The 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 forwardInvocation
Implementation can do anything, as long as the implementation of the function, will not crash error; The premise is not to do unimplemented methodsinvokeWithTarget
The 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