Objective-c runtime Three: Methods and messages

Source: Internet
Author: User
Tags set set



We discussed the processing of classes and objects in runtime, and the handling of member variables and attributes. In this chapter, we're going to start talking about the most interesting part of runtime: the message handling mechanism. We will discuss the sending of messages and the forwarding of messages in detail. But before we discuss the message, let's look at some of the things that are relevant to the method.


Underlying data type Sel


The SEL, also called the selector, is a pointer to the selector that represents a method, which is defined as follows:


typedef struct objc_selector *SEL;


The detailed definition of the objc_selector struct is not found in the <objc/runtime.h> header file. The selector method is used to represent the name of the run-time method. Objective-c at compile time, a unique integer identifier (an int type address) is generated based on the name and parameter sequence of each method, which is the SEL. As shown in the following code:



 
SEL sel1 = @selector(method1);
NSLog(@"sel : %p", sel1);

The above output is:


2014-10-30 18:40:07.518 RuntimeTest[52734:466626] sel : 0x100002d72


Between two classes, regardless of whether they are the parent class's relationship to the subclass, or if there is no such relationship, the sel of the method is the same as long as the method name is the same. Each method corresponds to an sel. So in objective-c the same class (and class inheritance system), there cannot be 2 methods with the same name, even if the parameter types are different. The same method can only correspond to one sel. This also results in a poor ability for objective-c to handle methods with the same method name and with the same number of arguments but different types. As in a class, define the following two methods:


- (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 as follows:


-(void)setWidthIntValue:(int)width;

-(void)setWidthDoubleValue:(double)width;


Of course, different classes can have the same selector, which is fine. When instance objects of different classes perform the same selector, they will find their corresponding imp in the respective list of methods according to selector.



All the SEL in the project consists of a set set, which is unique, so the SEL is unique. So, if we think of this method in the collection to find a method, we just need to find the corresponding sel of the method, the SEL is actually a hash of the method name of a string, and for the comparison of strings only need to compare their address on it, you can say the speed of the incomparable!! However, there is a problem, that is, the increase in number will increase the hash conflict caused by performance degradation (or no conflict, because it may also be used perfect hash). However, regardless of the method used to accelerate, if the total amount can be reduced (multiple methods may correspond to the same SEL), it will be the most sharp method. So, it's not hard to understand why SEL is just a function name.



In essence, the SEL is just a pointer to a method (to be exact, a key value that is hashed based on the method name, which uniquely represents a method), and it exists only to speed up the query of the method. This look-up process will be discussed below.



We can add new selector at run time, or we can get the existing selector at run time, we can get the SEL in the following three ways:


    1. Sel_registername function
    2. @selector () provided by the Objective-c compiler
    3. Nsselectorfromstring () method
IMP


The imp is actually a function pointer pointing 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 argument is a pointer to self (the memory address of the class instance if it is an instance method, or a pointer to a meta class if it is a class method), the second parameter is the method selector (selector), followed by the actual argument list of the method.



The SEL described earlier is to find the final implementation of the method imp. Since each method corresponds to a unique sel, we can quickly and accurately obtain the imp corresponding to it using the SEL, and the lookup process will be discussed below. When we get the imp, we have the entry point to execute this method code, so we can use the function pointer just like the normal C language function.



By acquiring IMP, we can skip the message passing mechanism of runtime and directly execute the function implemented by IMP, thus eliminating the series of lookups performed during runtime messaging, which is more efficient than sending messages directly to the object.


Method


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


ypedef 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 struct contains a SEL and imp, which is actually a mapping between the SEL and the Imp. With SEL, we can find the corresponding IMP, thus invoking the implementation code of the method. We will discuss the specific operating procedures below.


Objc_method_description


Objc_method_description defines a objective-c method, which is defined as follows:


struct objc_method_description { SEL name; char *types; };
Method related Operation function


Runtime provides a series of methods to handle the operations associated with the method. Includes the method itself and the SEL. In this section we describe these functions.


Method


The 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 the 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);


The Method_invoke function returns the actual implementation of the return value. The parameter receiver cannot be empty. This method will be more efficient than method_getimplementation and method_getname.



The Method_getname function returns a sel. If you want to get the C string for the method name, you can use Sel_getname (Method_getname (method)).



Method_getreturntype function, the type string is copied into DST.



Method_setimplementation function, note that the function return value is the implementation before the method.


Method Selector


Selector-related action 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, the message is not bound to the method implementation until run time. The compiler translates the message expression [receiver message] into a call to a message function, which is objc_msgsend. This function takes the message receiver and the method name as its underlying parameters, as shown in the following:


objc_msgSend(receiver, selector)


If there are other parameters in the message, the form of the method is as follows:


objc_msgSend(receiver, selector, arg1, arg2, ...)


This function completes all the things that are bound dynamically:


    1. First it finds the selector corresponding to the implementation of the method. Because the same method may have different implementations in different classes, we need to rely on the recipient's class to find the exact implementation.
    2. It invokes the method implementation and passes the recipient object and all parameters of the method to it.
    3. Finally, it will implement the returned value as its own return value.


The key to the message is the struct objc_class we discussed in the previous section, which has two fields that we are interested in distributing the message:


    1. Pointer to parent class
    2. A method of a class is published, that is, methodlists.


When we create a new object, we first allocate memory for it and initialize its member variables. Where the ISA pointer is also initialized, allowing the object to access the class and class inheritance system.



Demonstrates the basic framework for such a message:






When a message is sent to an object, Objc_msgsend gets to the struct of the class through the object's Isa pointer, and then finds the selector of the method in the method sub-publication. If selector is not found, the parent class is found through a pointer to the parent class in the Objc_msgsend struct, and the selector of the method is looked up in the sub-publication of the parent class. In this way, the NSObject class will be reached along the inheritance system of the class. Once positioned to selector, the function acquires the implementation entry point and passes in the corresponding parameters to execute the method's implementation. If the selector is not located at the end, it will go through the message forwarding process, which we'll discuss later.



To speed up the processing of messages, the runtime system caches the addresses of used selector and corresponding methods. We have discussed this point before and we will not repeat it.


Hide Parameters


Objc_msgsend has two hidden parameters:


    1. Message Receive Object
    2. Selector of the method


These two parameters provide the caller's information for the implementation of the method. The reason is that they are hidden because they are not declared in the source code of the defined method. They are inserted into the implementation code at compile time.



Although these parameters do not display declarations, they can still be referenced in your code. We can use self to refer to the recipient object and use _cmd to refer to 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, these two parameters we use more is self,_cmd in the actual use of relatively little.


Get method Address


The dynamic binding of methods in runtime allows us to write code more flexibly, such as we can forward the message to the object we want, or arbitrarily swap the implementation of a method. But the increased flexibility also leads to some performance losses. After all, we need to find the implementation of the method, not as straightforward as the function call. Of course, the caching of methods solves this problem to a certain extent.



As we mentioned above, if we want to avoid this dynamic binding, we can get the address of the method implementation, and then call it directly like a function call. This can improve the performance of the program, especially when we need to invoke a particular method frequently within a loop.



The NSObject class provides a methodforselector: method that allows us to obtain a pointer to a method and then invoke the implementation code through this pointer. We need to convert the Methodforselector: The returned pointer to the appropriate function type, and the function arguments and return values need to be matched.



Let's take a look at Methodforselector: using 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);


It is important to note that the first two parameters of a function pointer must be ID and sel.



This approach, of course, is only suitable for situations where the same method is frequently called in a case similar to a for loop to improve performance. 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 goes through the normal method invocation process. But what happens if an object cannot receive the specified message? By default, if the method is invoked as an [object message], the compiler will error if object cannot respond to message messages. But if it's a perform ... , you need to wait until the runtime to determine if object can receive a message. If not, the program crashes.



In general, when we are not sure whether an object can receive a message, Respondstoselector is called first: to judge. As shown in the following code:


 
if ([self respondsToSelector:@selector(method)]) {
    [self performSelector:@selector(method)];
}
 
 


However, we would like to discuss the situation of not using respondstoselector: judgment. This is the focus of our section.



When an object is unable to receive a message, it initiates the so-called "message forwarding" mechanism, through which we can tell the object how to handle an unknown message. By default, the object receives an unknown message that causes the program to crash, and 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 is actually thrown by NSObject's "Doesnotrecognizeselector" method. However, there are some steps we can take to get our program to execute specific logic and avoid a program crash.



The message forwarding mechanism is basically divided into three steps:


    1. Dynamic Method Parsing
    2. Alternate recipient
    3. Full forwarding


Let's take a look at these three steps in detail.


Dynamic Method Parsing


When an object receives an unknown message, it first calls the class method of the owning class +resolveinstancemethod: (instance method) or +resolveclassmethod: (class method). In this method, we have the opportunity to add a "processing method" to the unknown message. However, the premise of using this method is that we have implemented the "processing method", which can be dynamically added to the class by the Class_addmethod function at runtime. As shown in the following code:


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 implement the @dynamic attribute.


Alternate recipient


If the message cannot be processed in the previous step, runtime continues to tune the following methods:


- (id)forwardingTargetForSelector:(SEL)aSelector


If an object implements this method and returns a non-nil result, the object will be the new recipient of the message, and the message will be distributed to the object. Of course, this object cannot be self itself, otherwise there is an infinite loop. Of course, if we don't specify the appropriate object to handle aselector, we should call the implementation of the parent class to return the result.



Using this method is usually within the object, and there may be a series of other objects that can handle the message, and we can borrow the object to process the message and return it, so that it is handled by the object in person outside the object. 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 for us to forward the message to another object that can handle the message. However, this step cannot process the message, such as the parameters and return values of the action message.


Full message forwarding


If the unknown message cannot be processed in the previous step, the only thing that can be done is to enable the full message forwarding mechanism. The following methods are called:


- (void)forwardInvocation:(NSInvocation *)anInvocation


At this stage, the runtime system will give the message recipient the last chance to forward the message to another object. The Nsinvocation object creates an object that represents the message, encapsulating all the details related to the message that has not been processed in aninvocation, including selector, target, and parameters. We can choose to forward the message to other objects in the Forwardinvocation method.



Forwardinvocation: The implementation of the method has two tasks:


    1. Locates an object that can respond to messages encapsulated in Aninvocation. This object does not need to be able to handle all unknown messages.
    2. Use Aninvocation as a parameter to send a message to the selected object. Aninvocation will retain the result of the call, and the runtime will fetch the result and send it to the original sender of the message.


However, in this approach we can implement some more complex functions, we can modify the content of the message, such as the recovery of a parameter, and then to trigger the message. In addition, if you find that a message should not be handled by this class, you should call the parent class with the same name so that each class in the inheritance system has an opportunity to handle this call request.



There is also a very important question that we have to rewrite in the following ways:


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector


The message forwarding mechanism uses the information obtained from this method to create a Nsinvocation object. So we have to rewrite this method to provide a proper method signature for a given selector.



The complete example is shown below:


- (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];

    }

}


NSObject's Forwardinvocation: The method implementation simply calls the Doesnotrecognizeselector: method, which does not forward any messages. Thus, an exception is thrown if the unknown message is not processed in the three steps described above.



In a sense, forwardinvocation: Like the distribution center of an unknown message, these unknown messages are forwarded to other objects. Or you can send all unknown messages to the same receiving object like a transport station. This depends on the specific implementation.


Message forwarding and multiple inheritance


Looking back at the second and third steps, we can allow an object to establish a relationship with other objects to handle some unknown message, while on the surface it is still the object that is processing the message. With this relationship, we can simulate some of the features of multiple inheritance, allowing objects to "inherit" the attributes of other objects to handle things. However, there is an important difference between the two: multiple inheritance integrates different functions into an object, which causes the object to become too large and involves too much, while message forwarding decomposes the functionality into separate, small objects and somehow connects them and makes the appropriate message forwarding.



However, although message forwarding is similar to inheritance, some methods of nsobject can distinguish between the two. such as Respondstoselector: and Iskindofclass: can only be used for the inheritance system, but not for the forwarding chain. If we want this message forwarding to look like an inheritance, you 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 which we can add a lot of dynamic behavior to the program, although we rarely use these mechanisms directly in real-world development (such as calling Objc_msgsend directly), but understanding them helps us to learn more about the underlying implementations. In fact, in the actual coding process, we also have the flexibility to use these mechanisms to achieve some special functions, such as hook operation.


Reference
    1. Objective-c Runtime Reference
    2. Objective-c Runtime Programming Guide
    3. News of Objective-c Runtime (ii)
    4. The message of cocoa in layman's


Objective-c runtime Three: Methods and messages


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.