Objective-c runtime Three: Methods and messages

Source: Internet
Author: User

Base data type SEL
SEL, also known as selector, is a pointer to the selector of a method, which is defined as follows:

typedef struct objc_selector * SEL;
The detailed definition of the objc_selector structure was not found in the <objc / runtime.h> header file. The method selector is used to indicate the name of the runtime method. When Objective-C compiles, it will generate a unique integer identifier (address of type Int) according to the name and parameter sequence of each method. This identifier is SEL. As shown in the following code:

1
2
SEL sel1 = @selector (method1);
NSLog (@ "sel:% p", sel1);
The output above is:

2014-10-30 18: 40: 07.518 RuntimeTest [52734: 466626] sel: 0x100002d72
Between two classes, whether they are the relationship between the parent class and the child class, or there is no such relationship, as long as the method name is the same, the method SEL is the same. Each method corresponds to a SEL. Therefore, in Objective-C the same class (and the inheritance system of the class), there cannot be two methods with the same name, even if the parameter types are different. The same method can only correspond to one SEL. This results in Objective-C's poor ability to handle methods with the same method name and the same number of parameters but different types. For example, the following two methods are defined in a class:

-(void) setWidth: (int) width;

-(void) setWidth: (double) width;
Such a definition is considered a compilation error, so we can't do it like C ++, C #. Instead, it needs to be declared like this:

-(void) setWidthIntValue: (int) width;

-(void) setWidthDoubleValue: (double) width;
Of course, different classes can have the same selector, this is no problem. When different types of instance objects execute the same selector, they will find their corresponding IMPs in the respective method list according to the selector.

All SELs in the project form a Set collection. The characteristics of Set are unique, so SEL is unique. Therefore, if we think of searching for a method in this method set, we only need to find the SEL corresponding to this method. The SEL is actually a string hashed according to the method name, and only the comparison of strings is required. Just compare their addresses, it can be said that the speed is incomparable! !! However, there is a problem that an increase in the number will increase the performance of the hash conflict (or there is no conflict because the perfect hash may be used). But no matter what method is used to speed up, if you can reduce the total amount (multiple methods may correspond to the same SEL), it will be the sharpest method. Then, it is not difficult to understand why SEL is just a function name.

In essence, SEL is just a pointer to a method (to be precise, it is only a KEY value hashed according to the method name, which can uniquely represent a method). It exists only to speed up the query speed of methods. This search process is discussed below.

We can add a new selector at runtime, or we can get an existing selector at runtime. We can get the SEL in the following three ways:

sel_registerName function
@Selector () provided by Objective-C compiler
NSSelectorFromString () method
IMP
IMP is actually a function pointer to the first address of the method implementation. It is defined as follows:

id (* IMP) (id, SEL, ...)
This function uses the standard C calling convention implemented by the current CPU architecture. The first parameter is a pointer to self (if it is an instance method, it is the memory address of the class instance; if it is a class method, it is a pointer to a metaclass), and the second parameter is a method selector. Here is a list of the actual parameters of the method.

The SEL introduced earlier is to find the ultimate IMP of the method. Since each method corresponds to a unique SEL, we can easily and quickly obtain its corresponding IMP through the SEL. The search process will be discussed below. After obtaining the IMP, we have obtained the entry point for executing this method code. At this time, we can use this function pointer like calling a normal C language function.

By obtaining the IMP, we can skip the runtime message passing mechanism and directly execute the implementation of the function pointed to by the IMP. This saves a series of lookup operations during the runtime message passing process and is more efficient than sending a message directly to the object.

Method
After introducing SEL and IMP, we can talk about Method. Method is used to represent a method in a class definition, and is defined as follows:

typedef struct objc_method * Method;



struct objc_method {

    SEL method_name OBJC2_UNAVAILABLE; // method name

    char * method_types OBJC2_UNAVAILABLE;

    IMP method_imp OBJC2_UNAVAILABLE; // method implementation

}
We can see that the structure contains a SEL and IMP, which is actually equivalent to a mapping between SEL and IMP. With SEL, we can find the corresponding IMP and call the implementation code of the method. The specific operation process will be discussed below.

objc_method_description
objc_method_description defines an Objective-C method, which is defined as follows:

struct objc_method_description {SEL name; char * types;};
Method-related operation functions
Runtime provides a series of methods to handle method-related operations. Including the method itself and the SEL. In this section we introduce these functions.

method
Method operation related functions include the following:

// call the implementation of the specified method

id method_invoke (id receiver, Method m, ...);



// call the implementation of a method that returns a data structure

void method_invoke_stret (id receiver, Method m, ...);



// get method name

SEL method_getName (Method m);



// implementation of return method

IMP method_getImplementation (Method m);



// Get a string describing the method parameters and return value type

const char * method_getTypeEncoding (Method m);



// Get the method's return value type string

char * method_copyReturnType (Method m);



// Get the type string of the specified position parameter of the method

char * method_copyArgumentType (Method m, unsigned int index);



// return type string of return method by reference

void method_getReturnType (Method m, char * dst, size_t dst_len);



// return the number of parameters of the method

unsigned int method_getNumberOfArguments (Method m);



// The type string of the position parameter specified by reference return method

void method_getArgumentType (Method m, unsigned int index, char * dst, size_t dst_len);



// return the method description structure of the specified method

struct objc_method_description * method_getDescription (Method m);



// implementation of the set method

IMP method_setImplementation (Method m, IMP imp);



// exchange the implementation of the two methods

void method_exchangeImplementations (Method m1, Method m2);
● method_invoke function, which returns the actual implementation return value. The parameter receiver cannot be empty. This method is faster than method_getImplementation and method_getName.

● method_getName function returns a SEL. If you want to get the C string of the method name, you can use sel_getName (method_getName (method)).

● method_getReturnType function, the type string will be copied to dst.

● method_setImplementation function, note that the return value of this function is the previous implementation of the method.

Method selector
Selector-related operation functions include:

// return the name of the method specified by the given selector

const char * sel_getName (SEL sel);



// Register a method in the Objective-C Runtime system, map the method name to a selector, and return this selector

SEL sel_registerName (const char * str);



// Register a method in the Objective-C Runtime system

SEL sel_getUid (const char * str);



// compare two selectors

BOOL sel_isEqual (SEL lhs, SEL rhs);
● sel_registerName function: When we add a method to the class definition, we must register a method name in the Objective-C Runtime system to get the selector of the method.

Method invocation process
In Objective-C, messages are not bound to method implementations until runtime. The compiler translates the message expression [receiver message] into a call to a message function, objc_msgSend. This function takes the message receiver and method name as its basic parameters, as shown below:

objc_msgSend (receiver, selector)
If there are other parameters in the message, the method looks like this:

objc_msgSend (receiver, selector, arg1, arg2, ...)
This function does all the things for dynamic binding:

First it finds the method implementation corresponding to the selector. Because the same method may have different implementations in different classes, we need to rely on the receiver's class to find the exact implementation.
It calls the method implementation and passes it the receiver object and all parameters of the method.
Finally, it implements the returned value as its own return value.
The key to the message is the structure objc_class that we discussed in the previous chapter. This structure has two fields that we are focusing on in distributing the message:

Pointer to parent
A class method is published, namely methodLists.
When we create a new object, we first allocate memory for it and initialize its member variables. The isa pointer is also initialized, so that the object can access the class and the inheritance system of the class.

Demonstrates the basic framework of such a message:

When a message is sent to an object, objc_msgSend obtains the class structure through the object's isa pointer, and then finds the method selector in the method distribution. If the selector is not found, the parent class is found by the pointer to the parent class in the objc_msgSend structure, and the selector of the method is found in the parent's distribution. In this way, the NSObject class will be reached along the inheritance system of the class. Once the selector is located, the function will obtain the entry point of the implementation and pass in the corresponding parameters to execute the specific implementation of the method. If no selector is located at the end, the message forwarding process will be followed, which we will discuss later.

To speed up the processing of messages, the runtime system caches the used selector and the address of the corresponding method. This point was discussed earlier and will not be repeated.

Hide parameters
objc_msgSend has two hidden parameters:

Message receiver
Method selector
These two parameters provide the caller's information for the implementation of the method. They are hidden because they are not declared in the source code that defines the method . They are inserted into the implementation code at compile time.

Although these parameters are not explicitly declared, they can still be referenced in code. We can use self to reference the receiver object and _cmd to reference the selector. As shown in the following code:

-strange

{

    id target = getTheReceiver ();

    SEL method = getTheMethod ();



    if (target == self || method == _cmd)

        return nil;

    return [target performSelector: method];

}
Of course, we use more of these two parameters is self, _cmd is used less in practice.

Get method address
The dynamic binding of methods in Runtime gives us more flexibility when writing code. For example, we can forward messages to the object we want, or exchange the implementation of a method at will. However, the increase in flexibility also brings some losses in performance. After all, we need to find the implementation of a method, not as directly as a function call. Of course, method caching solves this problem to some extent.

We mentioned above that if we want to avoid this kind of dynamic binding, we can get the address of the method implementation, and then call it directly like a function. Especially when we need to call a specific method frequently in a loop, this way can improve the performance of the program.

The NSObject class provides a methodForSelector: method, so that we can get a pointer to the method, and then call the implementation code through this pointer. We need to convert the pointer returned by methodForSelector: to the appropriate function type, and the function parameters and return values need to match.

Let's see the use of methodForSelector: with the following code:

void (* setter) (id, SEL, BOOL);

int i;



setter = (void (*) (id, SEL, BOOL)) [target methodForSelector: @selector (setFilled :)];

for (i = 0; i <1000; i ++)

    setter (targetList [i], @selector (setFilled :), YES);
Note here that the first two parameters of the function pointer must be id and SEL.

Of course, this method is only suitable for situations where the same method is frequently called to improve performance in a situation similar to a for loop. In addition, methodForSelector: is provided by the Cocoa runtime; it is not a feature of the Objective-C language.

Message forwarding
When an object can receive a message, it will follow the normal method call flow. But what happens if an object cannot receive the specified message? By default, if the method is called as [object message], the compiler will report an error if the object cannot respond to the message. But if it is called in the form of perform ..., you need to wait until runtime to determine whether the object can receive message messages. If not, the program crashes.

Generally, when we are not sure whether an object can receive a certain message, we first call responsesToSelector: to judge it. As shown in the following code:

1
2
3
if ([self respondsToSelector: @selector (method)]) {
    [self performSelector: @selector (method)];
}
However, we want to discuss the case where responsesToSelector: is not used. This is the focus of our section.

When an object cannot receive a certain message, it will start the so-called "message forwarding" mechanism, through which we can tell the object how to handle unknown messages. By default, the object receives an unknown message, which will cause the program to crash. Through the console, we can see the following exception information:

-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940

*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940’
This exception message was actually thrown by the "doesNotRecognizeSelector" method of NSObject. However, there are a few steps we can take to make our program execute certain logic while avoiding program crashes.

The message forwarding mechanism is basically divided into three steps:

Dynamic method analysis
Alternate receiver
Full forwarding
Let's discuss these three steps in detail.

Dynamic method analysis
When the object receives an unknown message, it first calls the class method + resolveInstanceMethod: (instance method) or + resolveClassMethod: (class method) of the class it belongs to. In this method, we have the opportunity to add a "handling method" for the unknown message. However, the premise of using this method is that we have implemented the "handling method", which only needs to be dynamically added to the class through the class_addMethod function at runtime It's ok. The following code shows:

void functionForMethod1 (id self, SEL _cmd) {

   NSLog (@ "% @,% p", self, _cmd);

}



+ (BOOL) resolveInstanceMethod: (SEL) sel {



    NSString * selectorString = NSStringFromSelector (sel);



    if ([selectorString isEqualToString: @ "method1"]) {

        class_addMethod (self.class, @selector (method1), (IMP) functionForMethod1, "@:");

    }



    return [super resolveInstanceMethod: sel];

}
However, this scheme is more to achieve the @dynamic attribute.

Alternate receiver
If the message cannot be processed in the previous step, Runtime will continue to call the following methods:

-(id) forwardingTargetForSelector: (SEL) aSelector
If an object implements this method and returns a non-nil result, the object will act as a new receiver of the message, and the message will be distributed to this object. Of course, this object cannot be self itself, otherwise an infinite loop occurs. Of course, if we don't specify a corresponding object to handle aSelector, we should call the implementation of the parent class to return the result.

Using this method is usually inside the object, and there may be a series of other objects that can process the message. We can use these objects to process the message and return it. From the outside of the object, it is still the object who handles the message. As shown in the following code:

@interface SUTRuntimeMethodHelper: NSObject



-(void) method2;



@end



@implementation SUTRuntimeMethodHelper



-(void) method2 {

    NSLog (@ "% @,% p", self, _cmd);

}



@end



#pragma mark-



@interface SUTRuntimeMethod () {

    SUTRuntimeMethodHelper * _helper;

}



@end



@implementation SUTRuntimeMethod



+ (instancetype) object {

    return [[self alloc] init];

}



-(instancetype) init {

    self = [super init];

    if (self! = nil) {

        _helper = [[SUTRuntimeMethodHelper alloc] init];

    }



    return self;

}



-(void) test {

    [self performSelector: @selector (method2)];

}



-(id) forwardingTargetForSelector: (SEL) aSelector {



    NSLog (@ "forwardingTargetForSelector");



    NSString * selectorString = NSStringFromSelector (aSelector);



    // Forward the message to _helper for processing

    if ([selectorString isEqualToString: @ "method2"]) {

        return _helper;

    }



    return [super forwardingTargetForSelector: aSelector];

}



@end
This step is appropriate if we only want to forward the message to another object that can process the message. But this step cannot process the message, such as manipulating the parameters and return value of the message.

Full message forwarding
If you have not been able to process the unknown message in the previous step, the only thing you can do is enable the full message forwarding mechanism. The following methods are called:

-(void) forwardInvocation: (NSInvocation *) anInvocation
The runtime system will give the message receiver the last chance to forward the message to other objects at this step. The object creates an NSInvocation object that represents the message, and encapsulates all the details related to the unprocessed message in anInvocation, including the selector, target, and parameters. We can choose to forward the message to other objects in the forwardInvocation method.

The implementation of forwardInvocation: method has two tasks:

Locate objects that can respond to messages encapsulated in anInvocation. This object need not be able to handle all unknown messages.
Use anInvocation as a parameter to send a message to the selected object. anInvocation will keep the result of the invocation, and the runtime system will extract this result and send it to the original sender of the message.
However, in this method we can implement some more complicated functions. We can modify the content of the message, such as recovering a parameter, and then trigger the message. In addition, if you find that a message should not be processed by this class, you should call the method with the same name of the parent class, so that each class in the inheritance system has a chance to process this call request.

There is another important issue, we must rewrite the following methods:

-(NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector
The message forwarding mechanism uses the information obtained from this method to create an NSInvocation object. Therefore we must override this method to provide a suitable method signature for a given selector.

The complete example looks like this:

-(NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {

    NSMethodSignature * signature = [super methodSignatureForSelector: aSelector];



    if (! signature) {

        if ([SUTRuntimeMethodHelper instancesRespondToSelector: aSelector]) {

            signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector: aSelector];
}

    }



    return signature;

}



-(void) forwardInvocation: (NSInvocation *) anInvocation {

    if ([SUTRuntimeMethodHelper instancesRespondToSelector: anInvocation.selector]) {

        [anInvocation invokeWithTarget: _helper];

    }

}
The implementation of NSObject's forwardInvocation: method simply calls the doesNotRecognizeSelector: method, and it does not forward any messages. This way, if you don't handle unknown messages in the three steps described above, an exception will be thrown.

In a sense, forwardInvocation: is like a distribution center for unknown messages, forwarding these unknown messages to other objects. Or you can send all unknown messages to the same recipient as a transport station. It depends on the specific implementation.

Message forwarding and multiple inheritance
Looking back at the second and third steps, through these two methods we can allow an object to establish a relationship with other objects to process some unknown messages, but on the surface it is still the object that is processing the message. Through this relationship, we can simulate certain characteristics of "multiple inheritance", so that objects can "inherit" the characteristics of other objects to handle things. However, there is an important difference between the two: multiple inheritance integrates different functions into an object, which makes the object too large and involves too many things; and message forwarding breaks down the function into independent small ones. Objects, and connect them in some way, and do the corresponding message forwarding.

However, although message forwarding is similar to inheritance, some methods of NSObject can still distinguish the two. For example, responsesToSelector: and isKindOfClass: can only be used in the inheritance system, not in the forwarding chain. So if we want this kind of message forwarding to look like inheritance, we can override these methods, as shown in the following code:

-(BOOL) respondsToSelector: (SEL) aSelector

{

    if ([super respondsToSelector: aSelector])

        return YES;

    else {

        / * Here, test whether the aSelector message can *

         * be forwarded to another object and whether that *

         * object can respond to it. Return YES if it can. * /

    }



    return NO;}
summary
Here we have learned the basic mechanism of message sending and forwarding in Runtime. This is also the power of Runtime. Through it, we can add a lot of dynamic behavior to the program. Although we rarely use these mechanisms directly in actual development (such as directly calling objc_msgSend), understanding them helps us to To understand the underlying implementation. In fact, in the actual encoding process, we can also flexibly use these mechanisms to implement some special functions, such as hook operations.

Objective-C Runtime 3: Method and Message

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.