Detailed explanation of IOS message sending and forwarding examples

Time:2020-4-15

Preface

Objective-C is a dynamic language. It puts many things that static languages do during compilation and linking into runtime. The reason why we can have this feature is that we can’t do without the runtime library. Runtime solves the problem of how to find the calling method at runtime. Let’s learn from each other.

message sending

In Objective-C, method calls are called sending messages to objects:

//MyClass class
@interface MyClass: NSObject
- (void)printLog;
@end
@implementation MyClass
- (void)printLog {
NSLog(@"print log !");
}
@end
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
//Output: print log!

The [MyClass printlog] in the above code can also be written as follows:


((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));

[myClass printLog] is compiled to call the objc msgSend method.

Let’s look at the document definition of this method:


id objc_msgSend(id self, SEL op, ...);

Self: receiver of message OP: method name of message, C string…: parameter list

How does runtime find the concrete implementation of instance method?

Basic concepts

Before speaking, we need to understand some basic concepts: Objective-C is an object-oriented language, and objects are divided into instance objects, class objects, metaclass objects and root metaclass objects. They are related by a pointer called ISA, and the specific relationship is as follows:

Take our code above as an example:


MyClass *myClass = [[MyClass alloc] init];

Sort out the relationship:

  • MyClass is an instance object
  • MyClass is a class object
  • The metaclass of MyClass is the metaclass of nsobject
  • Nsobject is root class (class)
  • The superclass of nsobject is nil
  • The metaclass of nsobject is itself
  • The superclass of nsobject is nsobject

The corresponding position relationship in the above figure 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 class object is:% P", class);
Nslog (@ "MyClass metaclass object is:% P", metaclass);
Nslog (@ "the metaclass object of MyClass metaclass object is:% P", metaofmetaclass);
Nslog (@ "MyClass root metaclass object is:% P", rootmetaclass);
Nslog (@ "MyClass parent class is:% @", class_getsuperclass (class));
Nslog (@ "MyClass parent class's parent class is:% @, superofsuperclass);
Nslog (@ "the parent of the metaclass of MyClass parent class is:% @, superofmetaofsuperclass);

Nslog (@ "the nsobject metaclass object is:% P", object_getclass ([nsobject class]));
Nslog (@ "nsobject parent class is:% @", [[nsobject class] superclass]);
Nslog (@ "the parent class of an nsobject metaclass object is% @", [object_getclass ([nsobject class]) superclass];

// output:
The MyClass instance object is: 0x60c000000b8d0
MyClass class object is: 0x109ae3fd0
MyClass metaclass object is: * * * * 0x109ae3fa8
The metaclass object of MyClass metaclass object is: * * * * 0x10ab02e58**
MyClass root metaclass object is: 0x10ab02e58
MyClass parent class is: nsobject
The parent of MyClass is: (null)
The superclass of the metaclass of the MyClass superclass is: nsobject
The nsobject metaclass object is: 0x10ab02e58
The nsobject parent class is: (null)
The parent class of the nsobject metaclass object is: nsobject

It can be found that the output results are completely in line with our conclusions!

Now we can know the relationship between various objects:

The instance object finds the class object through the ISA pointer; the class object also finds the metaclass object through the ISA pointer; the metaclass object also finds the root metaclass object through the ISA pointer; finally, the ISA pointer of the root metaclass object points to itself. It can be found that nsobject is the core of the whole message mechanism, and most of the objects are inherited from it.

Search process

As mentioned above, an Objective-C method will be compiled into objc_msgsend. This function has two default parameters, self of ID type and op of sel type. Let’s first look at the definition of ID:


typedef struct objc_object *id;
struct objc_object {
 Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};

We can see that there is only one isa pointer to the class type in the obc_object structure.

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;

There are many parameters in it. You can see this line conspicuously:


struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;

It’s easy to understand by looking at the name. This methodlists is used to store the method list. Let’s take a look at the objc method list structure:


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;
}

The objc_method in it is the familiar method:


struct objc_method {
 SEL _Nonnull method_name OBJC2_UNAVAILABLE;
 char * _Nullable method_types OBJC2_UNAVAILABLE;
 IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}

Three parameters are saved in method:

  • Name of the method
  • Type of method
  • The specific implementation of the method is pointed to by the imp pointer

After layer by layer mining, we can understand the general logic of instance object calling methods:


MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
  • First compiled into((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
  • Follow the ISA pointer of the parameter MyClass to find the class object (class) of MyClass, that is, MyClass
  • Next, in the method list of MyClass, find the corresponding method
  • Finally, find the imp pointer in method, and execute the specific implementation

How to find and execute class methods of class objects?

As we have known above, instance objects are implemented in the method list saved in their class object (class) through isa pointer.

For example:


MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];

It can be understood that the printlog method is saved in MyClass.

So if it’s a class method, where is it stored?

We 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;

You can find this line:


Class _Nonnull isa OBJC_ISA_AVAILABILITY;

Isa here is also a pointer to a class. In the above, we also know that the ISA pointer of a class object points to a metaclass object. It’s not hard:

Class methods of class objects are saved in metaclass objects!

Class objects and metaclass objects are of class type, only the objects served are different. After finding the metaclass object, we naturally find the methodlists in the metaclass object. Next, we have the same process as the method search and call of the instance object.

On superclass

In Objective-C, a subclass calls a method. If there is no subclass but no implementation, the parent class will call the implementation of the parent class. Above, after finding methodlists, the process of finding methods is as follows:

How to improve the efficiency of method searching?

As we know above, the method is to find methodlists in class through isa pointer. If the subclass does not implement the corresponding method implementation, it will search along the parent class. There may be hundreds of millions of methods in the whole project. How to solve the performance problem?

For example:


for (int i = 0; i < 100000; ++i) {
 MyClass *myObject = myObjects[i];
 [myObject methodA];
}

This high-frequency call method a, if every call needs to traverse, the performance is very poor. So the class cache mechanism is introduced:

Class cache believes that when a method is called, it is more likely to be called later.

When you search for a method, you will first find it in the cache and return it directly; if you cannot find it, you will find it in the method list of class.

In the above definition of class, we can find cache:


struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;

It shows that the cache exists in the class. Each class has a method cache, not an object of each class.

Message forwarding

What if the method lists do not find the corresponding selectors?

//In viewcontroller. M (mytestprint method is not implemented)
[self performselector: @ selector (mytestprint:) withobject: @ ", Hello! ]

The system will provide three opportunities for remediation.

For the first time

+(BOOL) resolveInstanceMethod: (SEL) sel {} (instance method)
+(bool) resolveclassmethod: (SEL) sel {} (class method)

These two methods, one for instance method and one for class method. The return values are all bool.

Use example:

//In 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,"[email protected]:@");
  return YES;
 }else {
  return [super resolveInstanceMethod:sel];
 }
}

In the resolveinstancemethod: method, we only need to use the class uuaddmethod method to bind the unimplemented mytestprint: to mymethod to complete the forwarding, and finally return yes.

The second time


- (id)forwardingTargetForSelector:(SEL)aSelector {}

This method requires an ID to be returned. The usage scenario is generally to forward a method of class A to the implementation of class B.

Use example:

To forward to the – mytestprint: Method in the person class:


@interface Person : NSObject
@end
@implementation Person
- (void)myTestPrint:(NSString *)str {
 NSLog(@"ifelseboyxx%@",str);
}
@end
//In viewcontroller. M
- (id)forwardingTargetForSelector:(SEL)aSelector {
#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];
 }
}

Third time


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
- (void)forwardInvocation:(NSInvocation *)anInvocation {}

The first requires returning a method signature, and the second method forwards the specific implementation. The two depend on each other. Only when the correct method signature is returned can the second method be executed.

The forwarding function of this time is similar to that of the second time, which is to forward a method of class A to the implementation of class B. The difference is that the third forwarding is more flexible than the second. Forwardingtargetforselector: can only forward to one object fixedly; forwardinvocation: can let us forward to multiple objects.

Examples:

To forward to the – mytestprint: Method in the person class and the animal class:


@interface Person : NSObject
@end
@implementation Person
- (void)myTestPrint:(NSString *)str {
 NSLog(@"ifelseboyxx%@",str);
}
@end

@interface Animal : NSObject
@end
@implementation Animal
- (void)myTestPrint:(NSString *)str {
 NSLog(@"tiger%@",str);
}
@end
//In 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:"[email protected]:@"];
}
 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];
 }
}

A kind of If the third chance is reached and the corresponding implementation is not found, it will crash:


unrecognized selector sent to instance 0x7f9f817072b0

summary

Here, we can roughly understand the process of message sending and forwarding, and attach the flowchart:

Well, the above is the whole content of this article. I hope that the content of this article has some reference learning value for your study or work. If you have any questions, you can leave a message and exchange. Thank you for your support for developepaer.