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:

  • myClassIs an instance object
  • MyClassIs a class object
  • MyClassThe metaclass of the metaclass ofNSObjectThe metaclass
  • NSObjectRoot class (class)
  • NSObjectsuperclassnil
  • NSObjectIt is the metaclass ofoneself
  • NSObjectThe metaclasssuperclassisNSObject

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 byIMPPointer 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 referencemyClassisaPointer, findmyClassClass object (Class(That’s rightMyClass
  • Then, inMyClassList of methodsmethodLists, find the correspondingMethod
  • Finally foundMethodIn theIMPPointer 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