Objective-c is a dynamic language that takes a lot of the stuff that static languages do at compile time and link time and puts it into runtime. This feature is made possible by the Runtime library. The Runtime solves the problem of how to find methods to call at Runtime.
Message is sent
In Objective-C, a method call is called sending a message to an object:
/ / MyClass class
@interface MyClass: NSObject
- (void)printLog;
@end
@implementation MyClass
- (void)printLog {
NSLog(@"print log !");
}
@end
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
// Print log!
Copy the code
[myClass printLog] could also be written like this:
((void(*) (id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
Copy the code
[myClass printLog] is compiled to call the objc_msgSend method.
Let’s look at the documentation definition for this method:
id objc_msgSend(id self, SEL op, ...) ;Copy the code
Self: receiver of the message op: method name of the message, C string… : Parameter list
How does the Runtime find concrete implementations of instance methods?
Basic concept
Objective-c is an object-oriented language. Objects are divided into instance objects, class objects, metaclass objects, and root metaclass objects. They are related by a pointer called ISA, as shown below:
Take our code above as an example:
MyClass *myClass = [[MyClass alloc] init];
Copy the code
Sort out the relationship between them:
myClass
Is an instance objectMyClass
Is a class objectMyClass
The metaclass of the metaclass ofNSObject
The metaclassNSObject
Root class (class)NSObject
的superclass
为nil
NSObject
It is the metaclass ofoneselfNSObject
The metaclasssuperclass
isNSObject
The corresponding position relationship in the figure above is as follows:
Next, we use code to verify the above relationship:
MyClass *myClass = [[MyClass alloc] init];
Class class = [myClass class];
Class metaClass = object_getClass(class);
Class metaOfMetaClass = object_getClass(metaClass);
Class rootMetaClass = object_getClass(metaOfMetaClass);
Class superclass = class_getSuperclass(class);
Class superOfSuperclass = class_getSuperclass(superclass);
Class superOfMetaOfSuperclass = class_getSuperclass(object_getClass(superclass));
NSLog(@"MyClass instance object is: %p",myClass);
NSLog("MyClass object is: %p".class);
NSLog(@"MyClass metaclass object is: %p",metaClass);
NSLog(The metaclass of MyClass metaclass object is: %p",metaOfMetaClass);
NSLog(@"MyClass root metaclass object is: %p",rootMetaClass);
NSLog("MyClass parent is: %@",class_getSuperclass(class));
NSLog(The parent of MyClass is: %@",superOfSuperclass);
NSLog(The parent of the MyClass metaclass is: %@",superOfMetaOfSuperclass);
NSLog(@"NSObject metaclass is: %p",object_getClass([NSObject class]));
NSLog(@"NSObject parent is: %@"The [[NSObject class] superclass]);
NSLog(@"NSObject the parent of the metaclass object is: %@",[object_getClass([NSObject class]) superclass]);
/ / output:MyClass instance objects are:0x60c00000b8d0MyClass objects are:0x109ae3fd0The MyClass metaclass object is: ****0x109ae3fa8The metaclass object of MyClass metaclass object is: ****0x10ab02e58** MyClass root metaclass object is:0x10ab02e58The MyClass parent is:NSObjectThe parent of the MyClass metaclass is: (null) The parent of the MyClass metaclass is:NSObject
NSObjectMetaclass objects are:0x10ab02e58
NSObjectThe parent class is :(null)NSObjectThe parent class of a metaclass object is:NSObject
Copy the code
As you can see, the output is completely consistent with our conclusion!
Now we can know the relationship between various objects:
The instance object finds the Class object through the ISA pointer; Class objects also find metaclass objects through isa Pointers; The root metaclass object is also found through the ISA pointer; Finally, the isa pointer to the root metaclass object points to itself. You can see that NSObject is the heart of the entire messaging mechanism, and most objects inherit from it.
Looking for process
As mentioned earlier, an Objective-C method is compiled into objc_msgSend, which takes two default arguments, self of type ID and op of type SEL. Let’s first look at the definition of id:
typedef struct objc_object *id;
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
Copy the code
We can see that in the objc_Object structure, there is only one ISA pointer to Class type.
Let’s look at the definition of Class:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if ! __OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
Copy the code
There are a lot of parameters in there, and you can see this line:
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
Copy the code
It’s also easy to understand by name, so this methodLists is just a list of methods. Let’s look at the objc_method_list structure again:
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
Copy the code
Objc_method = objc_method;
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
Copy the code
Method holds three parameters:
- Method name
- Type of method
- The concrete implementation of the method by
IMP
Pointer to
After layer by layer digging, we can understand the general logic of the instance object calling method:
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
Copy the code
- It’s compiled into
((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
- Along the reference
myClass
的isa
Pointer, findmyClass
Class object (Class
(That’s rightMyClass
- Then, in
MyClass
List of methodsmethodLists
, find the correspondingMethod
- Finally found
Method
In theIMP
Pointer to a concrete implementation
How are class methods of class objects found and executed?
As we already know, instance objects are implemented through isa Pointers that find the list of methods stored in their Class.
Such as:
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
Copy the code
The printLog method is stored in MyClass.
So if it’s a class method, where is it stored?
Let’s review the definition of Class:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if ! __OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
Copy the code
You can find this line:
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
Copy the code
Here isa is also a pointer to a Class. We also saw above that isa Pointers to class objects point to metaclass objects. Then it is not difficult to conclude:
Class methods of class objects are stored in metaclass objects!
Class objects and metaclasses are both of Class type, only the objects served are different. Once you find the metaclass object, you naturally find methodLists in the metaclass object, and the same flow follows for method finding calls on the instance object.
How to improve the efficiency of method finding?
As we’ve seen above, the method is to look for methodLists in the Class using the ISA pointer. If the subclass does not implement the corresponding method implementation, it will look along the parent class. How does the whole project, maybe a billion ways, solve the performance problem?
Such as:
for (int i = 0; i < 100000; ++i) {
MyClass *myObject = myObjects[i];
[myObject methodA];
}
Copy the code
This high frequency call to methodA, if every call needs to be traversed, the performance is very poor. So Class Cache was introduced:
Class Cache says that when a method is called, it is more likely that it will be called later.
When a method is found, it is first searched from the cache and returned directly. If you can’t find it, go to the Class method list.
In the Class definition above, we can find the cache:
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
Copy the code
Caches exist in classes. Each class has a method cache.
About SuperClasses
In Objective-C, a subclass calls a method, and if there’s no subclass that doesn’t have an implementation, the superclass that does, it calls the implementation of the superclass. Above, after finding methodLists, the general process of finding Method is as follows:
Ps: The search process here is not that simple and may be traversed many times because we may add methods (such as categories) dynamically at run time. Traversal process also from time to time to query the cache table.
forward
What if the methodLists don’t find the corresponding selector?
// in viewController.m (myTestPrint method not implemented)
[self performSelector:@selector(myTestPrint:) withObject:"Hello!"];
Copy the code
The system will provide three opportunities for recovery.
For the first time,
+ (BOOLResolveInstanceMethod (SEL) SEL {} (instance method) +BOOLResolveClassMethod :(SEL) SEL {}Copy the code
These two methods, one for instance methods; One for the class method. The return value is Bool.
Example:
/ / ViewController. M
void myMethod(id self, SEL _cmd,NSString *nub) {
NSLog(@"ifelseboyxx%@",nub);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (sel == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
class_addMethod([self class],sel,(IMP)myMethod,"v@:@");
return YES;
}else {
return [superresolveInstanceMethod:sel]; }}Copy the code
In resolveInstanceMethod:, bind the unimplemented myTestPrint: to myMethod using class_addMethod, and return YES.
The second time
- (id)forwardingTargetForSelector:(SEL)aSelector {}
Copy the code
This method requires that an ID be returned. Usage scenarios typically involve forwarding A class A method to A class B implementation.
Example:
Want to forward to the Person class in the -myTestPrint: method:
@interface Person : NSObject
@end
@implementation Person
- (void)myTestPrint:(NSString *)str {
NSLog(@"ifelseboyxx%@",str);
}
@end
Copy the code
/ / ViewController. M - (id) in forwardingTargetForSelector aSelector: (SEL) {#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
return [Person new];
}else{
return[super forwardingTargetForSelector:aSelector]; }}Copy the code
The third time
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
- (void)forwardInvocation:(NSInvocation *)anInvocation {}
Copy the code
The first method returns a method signature, and the second method forwards the concrete implementation. The two depend on each other, and the second method is executed only if the correct method signature is returned.
This forwarding function is similar to the second one, which is to forward A method of class A to the implementation of class B. Different is the third time, forward relative to the second more flexible, forwardingTargetForSelector: fixed forwarded to only one object; ForwardInvocation: allows us to forward to multiple objects.
Example:
Want to forward to the Person class and Animal class in the -myTestPrint: method:
@interface Person : NSObject
@end
@implementation Person
- (void)myTestPrint:(NSString *)str {
NSLog(@"ifelseboyxx%@",str);
}
@end
Copy the code
@interface Animal : NSObject
@end
@implementation Animal
- (void)myTestPrint:(NSString *)str {
NSLog(@"tiger%@",str);
}
@end
Copy the code
/ / ViewController. M
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
Person *person = [Person new];
Animal *animal = [Animal new];
if ([person respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:person];
}
if([animal respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:animal]; }}Copy the code
⚠️ If no corresponding implementation is found by the third opportunity, the crash will occur:
unrecognized selector sent to instance 0x7f9f817072b0
Copy the code
conclusion
At this point, we can probably understand the process of sending and forwarding messages. Sorted out the general process, if you have any questions, welcome to ask:
Thanks for Sky’s correction and modification